[疯狂Java]TCP:TCP协议的概念、Socket通信、超时等待

来源:互联网 发布:如何雇佣网络水军 编辑:程序博客网 时间:2024/04/23 23:35

1. TCP协议的概念:

    1) 之前通过URLConnection建立连接、请求、响应所依赖的协议仅仅是IP协议,IP协议的终极目的是允许不同设备(不同OS)接入互联网;

    2) IP协议的不可靠性:

         i. IP协议仅仅负责数据的运输,可以说就是一个运输工;

         ii. 它只负责无脑地将数据包从一端送到另一端;

         iii. 非常无脑地送数据包,不管包中的数据是否出现错误,也不管数据包送的顺序等等,就算丢包了也不管,就是无脑送(无脑快递员,接到单子(包)就送,路上死了也不管); 

         iv. 不做任何检查,因此IP协议非常不可靠;

!!因此在计算机上安装IP协议软件只能保证计算机能正常的接受、发送数据,仅此而已;

    3) TCP/IP是分工协作的,是互补的:

         i. IP只负责运送数据,TCP协议负责数据安全;

         ii. TCP会在IP收到数据包后丢数据包进行检查,保证确认无误才接受,否则就会通知发送端重新发送那个数据包;

    4) TCP的机制:

         i. TCP是一种端对端的协议,它将通信双方(端口所代表通信软件)抽象成一个通信实体(即一个对象),即Socket,可以称为通信端,双方的通信其实就是两个Socket之间进行通信;

!!也就是在TCP的世界里(TCP层)不是计算机之间进行通信,而是Socket间进行通信;

         ii. 重发机制:发送数据后必须要等待对方确认,如果对方反馈说数据有问题就要重新发送,或者数据在中途丢掉了,一直等不到确认信息,那么就会超时重发;

         iii. TCP的自适应能力极强,即使在网络发生阻塞的情况下也能保证通信的可靠性;

    5) IP和TCP前者负责数据的传输,后者负责数据的安全,因此这两个协议是协作的互补的,凡是连接Internet的计算机必须同时安装这两个协议;

    6) TCP的C/S模型:

         i. TCP要求通信双方一方为客户端(Client),一方为服务器端(Server);

         ii. 客户端向服务器端发送请求,服务器端监听客户端发送的请求并对请求作出响应,这种请求-响应的过程就是数据交流的过程;

         iii. 客户端的职责:发送请求,接受响应数据;

         iv. 服务器端的职责:监听客户端的请求,接受请求并返回响应数据;


2. TCP服务器端——ServerSocket:

    1) ServerSocket类代表TCP服务器端,它的对象可以对请求进行监听并为作出响应提供支持;

    2) 构造器:

         i. ServerSocket(int port); // 指定Socket绑定的端口号

         ii. ServerSocket(int port, int backlog); // backlog是请求队列的长度,如果请求的数量非常多,为了不影响性能可能需要指定请求队列的最大长度,如果短时间内请求数量非常多使得队列超出backlog指定的长度,那么排在队列外的请求将忽略

         iii. ServerSocket(int port, int backlog, InetAddress bindAdress); // 还要指定本Socket所绑定的IP地址

!!默认会使用本机默认的IP地址来绑定服务器端Socket(即当前正在上网的IP地址),但也允许一台计算机有多个IP地址(不同通信程序使用不同IP地址,如果有这种需要的话),此时就需要自定绑定指定的IP地址了

    3) 监听请求:Socket accept(); // 监听请求,请求未到达之前线程一直处于阻塞状态(等待),请求到来后线程就绪并运行(同时接受到请求),返回的是代表请求端的Socket对象

!!一般服务器要求能不停地监听用户的请求,因此会用一个死循环来执行accept,并且每收到一个请求就开辟一个线程为其服务;

while (true) {Socket reqSock = sock.accept();new Thread(MyServerThread(reqSock), "XXX").start();}
!!使用多线程是为了同时相应多个请求,否则就只能顺序排队,影响用户体验;

!!Socket是客户端Socket的类型!

    4) 当Socket使用完毕后应该及时使用close方法关闭资源:void close(); // 它是ServerSocket的方法


3. TCP客户端——Socket:

    1) 客户端的Socket代表类就是Socket(很奇怪,服务器端是ServerSocket,并且Socket不是ServerSocket的父类!!这两个类是相互平行的),Socket类就代表客户端!

    2) 构造器:既然是客户端那肯定是要指定连接到哪个服务器的,因此构造器中必须指定目标服务器的IP地址

         i. Socket(InetAddress/String host, int port); // 指定远程Socket的IP地址和端口,可以用String或InetAddress指定,通常String更好用,比如"192.222.143.0",很方便

         ii. Socket(InetAddress/String host, int port, InetAddress localAddr, int localPort); // 前两个还是指定远程的Socket,后两个则指定本Socket绑定的IP地址和端口,适用于多IP地址的情形,绑定IP必须用InetAddress类型

    3) 当使用构造器构造出Socket对象的一瞬间就向服务器发送连接请求了,服务器的accept方法直接回接受该请求并建立连接,进行接下来的通信!!


4. 双方通信:

    1) 双方Socket建立连接之后就可以正常通信了;

    2) 通信方式很简单,还是获取InputStream和OutputStream对象,然后用普通Java的IO方式进行远程通信,因此可以看到Java在IO体系上设计的良苦用心了,不管是文件、块设备、Socket都可以简单的使用数据流操作,完全隐藏了它们底层的不同;

    3) 通信需要利用客户端的Socket的get方法来获取InputStream(对内输入,获取远程数据)和OutputStream(对外输出,向远程发送数据):ServerSocket没有get InputStream和OutputStream的方法,通信只能靠Socket!

         i. OutputStream Socket.getOutputStream(); // 在客户端调用以向服务器端发送数据

         ii. InputStream Socket.getInputStream(); // 在客户端调用以从服务器端接受数据

!!那么如何在服务器端发送和接受数据呢?

        a. 首先在服务器端accept请求后会返回一个代表客户端的Socket对象sock,而该对象不就可以调用get方法获取OutputStream和InputStream嘛!

        b. 客户端的Output对于服务器端就变成Input了,因此sock.getInputStream获得的是客户端请求的数据;

        c. 同理,客户端的Input对于服务器端就是Output的东西,因此向往客户端发送响应数据就要调用sock.getOutputStream;

!!总的来说就这么简单,通信是以客户(客户端)为主体进行的,所有的通信数据都包裹在代表客户端的Socket对象中,以上的过程就是把客户端的Socket对象当成数据包,Ouput就是往数据包里写数据,Input就是从数据包中读数据,剩下的就是数据包的传递了(而在编程中我们并不需要关心),因此不必死记在客户端/服务器端发送/接受是应该调用getInput还是getOutput了,只要理解成数据包的IO即可,非常简单;

    4) Socket类的输入输出流(InputStream和OutputStream)分别用两个缓冲区实现的,因此不必同时使用InputStream和OutputStream会导致数据相互覆盖等问题,因为它俩的缓冲区是相互独立的;


5. 超时等待:

    1) 如果不想让建立连接、读写(远程)操作等的等待时间过长(可能流量大导致网速慢或其它异常原因)则可以设置超时等待时间,超过该时间就自动抛出异常通知超时了;

    2) 因为等待的时候线程是阻塞的(没发生任何操作),因此不能因此白白浪费CPU时间;

    3) 两种设置超时等待的方法:超时等待时间设定以后的作用范围是全局的,即设置时候的所有通信操作都会沿用该超时等待时间(读、写等等)

         i. 客户端:先建立一个空的Socket对象(空的构造器),然后使用connect方法请求连接,在该方法中指定超时等待时间(因为Socket的构造器并没有提供指定超时等待事件的版本,但是在构造Socket时一旦指定了远程IP就会立即建立连接,而现在就是要在建立连接的这一环节也要超时等待)

            a. connect:void connect(SocketAddress endpoint, int timeout); // timeout即超时等待时间,单位是毫秒

!SocketAddress不是InetAddress的子类,因此不能穿InetAddress的对象,毕竟TCP通信是要同时指定IP和端口的,SocketAdress只是一个接口,其实现类是InetSocketAddress,是真正意义上的TCP地址(IP+端口),其构造器:InetSocketAddress(String hostname, int port); // hostname是诸如"x.x.x.x"的数字字符串IP地址

            b. 完整的过程:

Socket sock = new Socket();sock.connect(new InetSocketAddress("127.0.0.1", 50), 5000); // 超时等待时间是5秒
         ii. 服务器端/客户端通用方法:服务器并不需要和谁建立通信,只是被动地等待请求,收到请求后用客户端的Socket进行通信,因此服务器端没有什么建立连接的超时等待,直接使用ServerSocket和Socket的setSoTimeout方法设置即可,服务器Socket和客户端Socket都能设置,从调用起全局生效:void setSoTimeout(int timeout);

!!超时后会抛出SocketTimeoutException异常,要及时处理;


6. 示例:广播(服务器将其中一个客户端发送过来的话广播给所有当前连接上服务器的客户端),即群聊功能

服务器端:

public class Server {private int no = 0; // 为请求者编号// 保存当前连接的客户端Socketprivate List<Socket> sockList = Collections.synchronizedList(new ArrayList<Socket>());class ServerThread implements Runnable {int no;Socket s;BufferedReader br;public ServerThread(Socket s, int no) {this.s = s;this.no = no;try {br = new BufferedReader(new InputStreamReader(s.getInputStream()));} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}private String getContentByLine() {try {return br.readLine();} catch (IOException e) { // 该请求可能已被客户端关闭// TODO Auto-generated catch blocke.printStackTrace();sockList.remove(s); // 因此需要从列表中移除}return null;}@Overridepublic void run() {// TODO Auto-generated method stubString content = "";while ((content = getContentByLine()) != null) {// 只要客户端不关闭Socket,那么输入输出流就是一直开着的// 服务器端会一直等待输入,永远也到不了EOF使getContentByLine返回nullfor (Socket s: sockList) { // 对每一个连接的客户端都发送聊天信息PrintStream ps;try {ps = new PrintStream(s.getOutputStream());ps.println("#" + no + " says: " + content);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}}public void init() throws IOException {ServerSocket ss = new ServerSocket(30000, 10, InetAddress.getByName("192.168.96.1")); // 使用默认IP,端口3000while (true) {Socket s = ss.accept();no += 1;System.out.println("#" + no + " connected!");sockList.add(s);new Thread(new ServerThread(s, no)).start();}}public static void main(String[] args) throws IOException {// TODO Auto-generated method stubnew Server().init();}}
客户端:

public class Client {class ClientThread implements Runnable {private Socket s;public ClientThread(Socket s) {this.s = s;}@Overridepublic void run() {// TODO Auto-generated method stubtry {String content = "";BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));while ((content = br.readLine()) != null) { // 不断读取服务器发来的广播消息System.out.println(content);}} catch (Exception e) {e.printStackTrace();}}}public void init() throws UnknownHostException, IOException {Socket s = new Socket("192.168.96.1", 30000);new Thread(new ClientThread(s)).start(); // 建立连接后马上监控广播消息PrintStream ps = new PrintStream(s.getOutputStream()); // 输出流定向到远程String line = "";BufferedReader br = new BufferedReader(new InputStreamReader(System.in));while ((line = br.readLine()) != null) { // 从控制台键盘读取输入ps.println(line); // 发送到远程}}public static void main(String[] args) throws UnknownHostException, IOException {// TODO Auto-generated method stubnew Client().init();}}
!运行的时候一定要先运行服务器,否则客户端连接不到服务器而异常终止(本示例非常简单,没有很好的异常处理机制);

!!可以多开几个客户端同时测试,就可以达到群聊的效果了;



1 0
原创粉丝点击