JAVA网络编程之——TCP通信

来源:互联网 发布:手机拍照扫描软件 编辑:程序博客网 时间:2024/06/10 15:04
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.创建服务器端SocketServerSocket 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成功,获取输入输出流InputStreamips = s.getInputStream();OutputStreamops = 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和端口,因此可以鉴别)。

0 0
原创粉丝点击