您的位置:首页 > 理论基础 > 计算机网络

JAVA网络编程之——TCP通信

2015-06-26 12:19 567 查看
JAVA网络编程之——TCP通信

TCP是面向连接的,所以是C/S架构,服务器端ServerSocket首先要创建启动,然后监听某一个端口,等待Client的Socket连接,如果有连接发过来,就创建一个专门的Socket_New连接用于和这个Client的Socket进行通信。
那么问题来了:
1. Client的Socket连接有自已的IP和Port,那服务器与它通信的这个Socket_New的端口号是不是ServerSocket监听的端口号呢?建立连接——>数据通信,这个具体流程怎样?
2. 服务器端 多线程解决多Client的方法。

首先,看一下Client和Server的交互过程:



Socket一些常用方法 :

getInetAddress();   远程服务端的IP地址

getPort();       远程服务端的端口

getLocalAddress()   本地客户端的IP地址

getLocalPort()    本地客户端的端口

getInputStream(); 获得输入流

getOutStream();    获得输出流

值得注意的是,在这些方法里面,最重要的就是getInputStream()和getOutputStream()了。一般不直接使用,因为网络上的流是字节流,JAVA中一般转成字符流,再包装一下进行更方便的处理。

下面看一下最基本的TCP Server的编写步骤,客户端可以用Windows自带的telnet来测试。
//1.创建服务器端Socket
ServerSocket ss = new ServerSocket(10001);

System.out.println("等待客户端连接.....");
//2.阻塞等待客户端连接
Socket s = ss.accept();
System.out.println("连接成功!");

//3.程序运行到此处表明连接成功,获取Socket的输入输出流
OutputStream ops = s.getOutputStream();
InputStream  ins = s.getInputStream();

//4.服务器返回给客户端一个信息
ops.write("Hello,欢迎访问TCP服务器".getBytes());

//5.把网络字节流转换为字符流再包装成Buffer流
BufferedReader br = new BufferedReader(new InputStreamReader(ins));

//6.利用BufferReader的"行读"功能,读取客户端输入的一行数据
// 此处会阻塞读入,直到客户端输入一行数据(telnet输入完成回册)
System.out.println(br.readLine());

//7.关闭包装类会自动关闭包装类中所有的底层类
br.close();
s.close();
ss.close();


服务器要响应多个Client的连接就需要用到多线程,每当有一个Client发出连接请求,都创建一个新的线程和这个Client进行专线通信。
下面是最基本的一个多线程处理Client请求的例子,先看主程序:
public class TCPServerTest
{

public static void main(String [] args) throws IOException
{
//1.创建TCP Server,监听10001号端口
ServerSocket ss = new ServerSocket(10001);

while(true)
{
//2.等待客户端连接
System.out.println("等待客户端连接...");
Socket s = ss.accept();
System.out.println("连接成功!");
//3.创建一个新线程和上面的客户端进行专线连接
new Thread(new Server(s)).start();

//4.打印连接信息,然后等待下一个连接
System.out.println("新线程创建成功,客户端信息如下:");
System.out.println("    客户端IP: " + s.getInetAddress().getHostAddress());
System.out.println("    客户端Port: " + s.getPort());
System.out.println();

}
}
}


上面代码中, new Server(s)是一个实现了Runnable接口的多线程类的对像,把ss.accept()返回来的socket连接给这个多线程对像,它就知道自已要干什么了(和哪个Client的socket进行通信)。
Server代码如下:
//服务器线程
public class Server implements Runnable
{
private Socket s;

//构造器,主线程传过来个socket,子线程才知道和哪个Socket连接
public Server(Socket s)
{
this.s = s;
}

public void run()
{
//程序执行到此说明服务器响应Client并新建Thread成功,获取输入输出流
InputStream	ips = s.getInputStream();
OutputStream	ops = s.getOutputStream();

//将网络字节流转换为字符流,再包装成Buffer流
BufferedReader br = new BufferedReader(new InputStreamReader(ips));

String recvStr = null;

while(true)
{
//阻塞等待客户端输入一行数据
recvStr = br.readLine();
//如果客户端输入quit则退出循环,关闭连接
if(recvStr.equalsIgnoreCase("quit"))
{
break;
}
//把收到的数据回打给客户端
String str = "服务器已收到,内容为:" + recvStr;
ops.write(str.getBytes());
}

br.close();
s.close();

}


上面代码没有进行异常处理,这样编译是不通过的,但是为了简洁直观,这里只贴出了关键内容。

运行服务器程序,进行测试如下:



经测试,达到了预期的效果,每个Client可以和Server单独通信。

accept函数主要用于服务器端,默认会阻塞进程,直到有一个客户请求连接,建立好连接后,它返回的一个新的套接字
Socket_New(),此后,服务器端即可使用这个新的套接字Socket_New()与该客户端进行通信,而ServerSocket
则继续用于监听其他客户端的连接请求。并且新的Socket_New是否为阻塞和非阻塞属性,与监听的Socket一样,监听的Socket为阻塞,则新的Socket_New也为阻塞,反之一样。
至此,我的困惑产生了,这个新的套接字
Socket_New与监听套接字ServerSocket是什么关系?它所代表的socket对象包含了哪些信息?Socket_New是否占用了新的端口与客户端通信?
设想一下,由于网站的服务器也是一种TCP服务器,使用的是80端口,并不会因客户端的连接而产生新的端口给客户端服务,该客户端依然是向服务器端的80端口发送数据,其他客户端依然向80端口申请连接。因此,可以判断,Socket_New并没有占用新的端口与客户端通信,依然使用的是与监听套接字socketfd_new一样的端口号。再说了,在一台机器中,端口的使用数量是65535,是有限的,不可能无限制的占用新端口来使用,那这么说,难道一个端口可以被两个socket对象绑定?当客户端发送数据过来的时候,究竟是与哪一个socket对象通信呢?

个人理解如下:

首先,一个端口肯定只能绑定一个socket。我认为,服务器端的端口在new ServerSocket(int port)的时候已经绑定到了监听套接字ss所描述的对象上,accept函数新创建的Socket_New对象其实并没有进行端口的占有,而是复制了ServerSocket ss的本地IP和端口号,并且记录了连接过来的客户端的IP和端口号。

那么,当客户端发送数据过来的时候,究竟是与哪一个socket对象通信呢?

客户端发送过来的数据可以分为2种,一种是连接请求,一种是已经建立好连接后的数据传输。由于TCP/IP协议栈是维护着一个接收和发送缓冲区的。在接收到来自客户端的数据包后,服务器端的TCP/IP协议栈应该会做如下处理: 1. 如果收到的是请求连接的数据包,则传给监听着连接请求端口的ServerSocket 套接字,进行accept处理;

2. 如果是已经建立过连接后的客户端数据包,则将数据放入接收缓冲区。

这样,当服务器端需要读取指定客户端的数据时,则可以利用socketfd_new 套接字通过recv或者read函数到缓冲区里面去取指定的数据(因为socketfd_new代表的socket对象记录了客户端IP和端口,因此可以鉴别)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: