TCP协议基础:
要使两台计算机彼此能进行通信,必须使两台计算机使用同一种“语言”,IP协议只保证计算机能发送和接收数据,但不能解决数据在传输过程中可能出现的问题。为了解决这个问题,需要安装TCP协议来提供可靠并且无差错的通信服务。
TCP协议被称作一种端与端协议。它对两台计算机之前的连接起了重要作用,当一台计算机需要与另外一台远程计算机连接时,TCP协议会让它们建立一个连接:用于发送和接收数据的虚拟链路。如下图:
在上图中,我们可以看到TCP通信的两个通信实体之间是没有区分服务端和客户端的,这是因为此图是两个通信实体已经建立虚拟链路之后的示意图。在两个通信实体没有建立虚拟链路之前,必须有一个通信实体先做出“主动姿态”,主动接收来自其他通信实体的连接请求。在java中,能后接受其他通信实体连接请求的类是ServerSocket。
ServerSocket 类:
ServerSocket类有以下构造方法:
1. ServerSocket(int port):用指定的端口port来创建一个ServerSocket。port的范围应在0~65535之间选取
2. ServerSocket(int port , int backlog): 增加一个用来改变连接队列长度的参数backlog
3. ServerSocket(int port , int backlong ,InetAddress localAddr): 在方法二的基础上添加指定的ip地址
ServerSocket 的主要方法:
1. accept():如果接收到一个通信实体Socket的连接请求,该方法将返回一个与该通信实体Socket对应的Socket,并搭建虚拟链路,如上示意图;若没有接收到连接请求,该方法会一直处于等待状态,线程也被阻塞。
2. close(): 关闭ServerSocket
Socket类:
Socket构造方法如下:
1. Socket(InetAddress/String remoteAddress,int port):创建连接到指定远程主机,远程端口的Socket;若该构造没有传参,远程主机默认使用本地主机的默认IP地址,远程端口默认使用系统动态分配的端口
2. Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr , int localPort):创建连接到远程主机,远程端口的Socket,并指定本地IP地址和本地端口
Socket的主要方法:
1. InputStream getInputStream():返回该Socket对象对应的输入流,可以通过该输入流从Socket中取出数据
2.OutputStream getOutputStream(): 返回该Socket对象对应的输出流,可以通过该输出流从Socket中传递数据
3.setSoTimeout(int timeout):设置两个Socket连接时间,超过指定时间后,若是两个SOcket正在进行读,写操作时,会抛出异常
4.shutdownInput() : 关闭该Socket的输入流,关闭之后,Socket只可以通过输出流传递数据
5.shutdownOutput() : 关闭该Socket的输出流,关闭之后,Socket只可以通过输入流取出数据
6.close() :关闭该Socket,当其中一个Socket关闭时,对应的虚拟链路结束
简单的TCP通信:
创建接收其他通信实体请求的通信实体(一般称该通信实体未服务端)的类,该类一共有两个方法server()和init(),server和init通信的内容时差不多相同的,不同的是server方法直接关闭了Socket,而init是先关闭了Socket的输入流,在关闭Socket
class SocketNet{ public void server(){ try{ //监听并接收来自端口号为30000的请求 ServerSocket serverSocket = new ServerSocket(30000); while(true){ Socket socket = serverSocket.accept(); PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println("您好,您收到了服务器的响应"); ps.close(); socket.close(); } }catch(Exception e){} } //半关闭Socket public void init(){ try{ ServerSocket serverSocket = new ServerSocket(30000); Socket socket = serverSocket.accept(); System.out.println("等待客户端发出发出请求"); PrintStream ps = new PrintStream(socket.getOutputStream()); System.out.println("收到客户端发出请求"); ps.println("服务器的第一行数据"); ps.println("服务器的第二行数据"); //关闭socket输出流 socket.shutdownOutput(); System.out.println("socket是否关闭:"+socket.isClosed()); Scanner scan = new Scanner(socket.getInputStream()); while(scan.hasNextLine()){ System.out.println(scan.nextLine()); } scan.close(); socket.close(); serverSocket.close(); System.out.println("socket是否关闭:"+socket.isClosed()); }catch(Exception e){} } }
public class Net{
public static void main(Stirng[] args){
SocketNet socketNet = new SocketNet();
System.out.println("启动服务端");
// socketNet.server();
//半关闭Socket
socketNet.init();
}
}
创建用于发送请求的通信实体(称之为客户端):
public class MyClient{ public void client(){ try{ //不停的接收来自指定ip地址且端口号为30000的响应 Socket socket = new Socket("127.0.0.1",30000); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = null; while((line = br.readLine()) != null){ System.out.println("来自服务器的数据:"+line); } br.close(); }catch(Exception e){} } public static void main(String[] args){ // TODO Auto-generated method stub MyClient myClient = new MyClient(); //启动客户端 System.out.println("启动客户端"); myClient.client(); } }
运行服务端的server方法以及客户端(先运行服务端,在运行客户端),客户端结果如下:
启动客户端
来自服务器的数据:您好,您收到了服务器的响应
运行服务端的init方法以及客户端(先运行服务端,在运行客户端),客户端结果如下:
启动客户端
来自服务器的数据:服务器的第一行数据
来自服务器的数据:服务器的第二行数据
以上代码只是对TCP通信的简单编写,下面用java搭建一个接收请求的通信实体,并且用浏览器当作客户端进行访问class SocketNet public void browserSocket(){
class SocketNet{ public void browserSocket(){ try{ System.out.println("等待浏览器的请求"); ServerSocket server=new ServerSocket(9990); Socket socket=server.accept(); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); if(br != null){ System.out.println("浏览器请求成功"); while(br.readLine() != null){ System.out.println("浏览器的请求内容:"+br.readLine()); break; //这里由于IE浏览器的请求内容一直在编写,为了早些响应,就踢出循环体了;若想看请求内容,可以使用谷歌浏览器,只是谷歌浏览器不能响应内容 } System.out.println("浏览器请求内容完毕,服务器响应浏览器的请求"); }else{ System.out.println("浏览器请求失败"); } PrintWriter ps = new PrintWriter(socket.getOutputStream()); ps.println("已收到你的请求"); ps.close(); br.close(); }catch(Exception e){} } } public class Net{ public static void main(String[] args){ SocketNet socketNet = new SocketNet(); System.out.println("启动服务端"); //创建服务端,以浏览器访问作为客户端进行Socket通信 socketNet.browserSocket(); } }
运行后,在IE浏览器上输入127.0.0.1:端口号或者实际的ip地址:端口号进行访问:
java运行的结果如下:
启动服务端 等待浏览器的请求 浏览器请求成功 浏览器的请求内容:Accept: text/html, application/xhtml+xml, image/jxr, */* 浏览器请求内容完毕,服务器响应浏览器的请求
IE浏览器访问的结果如下:要注意的是,谷歌浏览器响应不了。
TCP通信与多线程实现TCP/IP聊天室应用:各个客户端可以送到来自其他客户端包括自己本身发给服务端的信息:
1,创建用于接收其他通信实体请求的通信实体
class SocketThread{ /** * 搭建Socket多线程 服务端 * */ //定义保存所有Socket的ArrayList,并将其包装为线程安全的 public static List<Socket> socketList =Collections.synchronizedList(new ArrayList<>()); public void server(){ try{ ServerSocket serverSocket = new ServerSocket(30000); while(true){ Socket s = serverSocket.accept(); socketList.add(s); //每当与客户端连接后启动一个ServerThread线程为该客户端服务 new Thread(new ServerThread(s)).start(); } }catch(Exception e){} } //负责处理每个线程通信的线程类 class ServerThread implements Runnable{ Socket s = null; //定义当前线程所处理的Socket BufferedReader br = null; //定义当前线程所处理的Socket对应输入流 public ServerThread(Socket s){ try{ //初始化 this.s = s; br = new BufferedReader(new InputStreamReader(s.getInputStream())); }catch(Exception e){} } @Override public void run() { // TODO Auto-generated method stub try{ String content = null; //采用循环不断地从Socket中读取客户端发送过来的数据 while((content = readFromClient())!= null){ //遍历所有的客户端,并对其发送消息 for(Socket s : SocketThread.socketList){ PrintStream ps = new PrintStream(s.getOutputStream()); ps.println(content); } } }catch(Exception e){} } public String readFromClient(){ try{ return br.readLine(); }catch(Exception e){ //捕获异常,表明客户端Socket已经关闭,从Socket列表中删除该Socket SocketThread.socketList.remove(s); } return null; } } } public class Net{ public static void main(String[] args){ System.out.println("启动服务端"); SocketThread socketThread = new SocketThread(); socket.server(); } }
2.创建客户端
class ClientThread implements Runnable{ Socket s = null; //该线程负责处理的Socket BufferedReader br = null; //该线程所处理的Socket对应的输入流 public ClientThread(Socket s){ try{ //初始化 this.s = s; br = new BufferedReader(new InputStreamReader(s.getInputStream())); }catch(Exception e){} } @Override public void run() { // TODO Auto-generated method stub try{ String content = null; //不断地读取Socket输入流中的内容,并将这些内容打印输出 while((content = br.readLine())!=null){ System.out.println(content); } }catch(Exception e){} } } public class MyClient { /** * Socket多线程客户端 * */ public void clientThread(){ try{ Socket s = new Socket("127.0.0.1",30000); //客户端启动ClientThread线程不断地读取来自服务端的数据 new Thread(new ClientThread(s)).start(); //获取该Socket对应输出流 PrintStream ps = new PrintStream(s.getOutputStream()); String line = null; //不断地读取键盘输入 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while((line = br.readLine())!= null){ //将从键盘输入的内容写入Socket对应的输出流中 ps.println(line); } }catch(Exception e){} } public static void main(String[] args){ // TODO Auto-generated method stub MyClient myClient = new MyClient(); System.out.println("启动客户端"); //多线程Socket myClient.clientThread(); } }
运行服务端,多运行几个客户端,并在各个客户端上控制台输入回车,运行效果如下:
运行效果1:服务端: 客户端1: 客户端2:
----------------------------------------------------------------------------------------------------------------------------------------
运行效果2:服务端: 客户端1: 客户端2:
总结:TCP协议最大的好处在于它可以自己定义哪个通信实体时服务端,哪个通信实体是客户端。在我们web开发时,所接触到的tomcat以及apache服务器,这些服务器都是功能强大的Socket通信,因此我们也可以通过TCP/IP协议以及Socket类来编写实现一个服务器。