创建TCP服务端
1.创建一个ServerSocket对象。
2.调用accept()方法接收客户端请求。
3.从Socket中获取I/O流。
4.对I/O流进行读写操作,完成与客户端的交互。
5.关闭I/O流和Socket
1 import java.io.InputStream; 2 import java.io.OutputStream; 3 import java.net.ServerSocket; 4 import java.net.Socket; 5 6 public class Server { 7 public static void main(String[] args) throws Exception { 8 // 监听指定的端口 9 int port = 55533; 10 ServerSocket server = new ServerSocket(port); 11 12 // server将一直等待连接的到来 13 System.out.println("server将一直等待连接的到来"); 14 Socket socket = server.accept(); 15 // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取 16 InputStream inputStream = socket.getInputStream(); 17 byte[] bytes = new byte[1024]; 18 int len; 19 inputStream.read(bytes); 20 // StringBuilder s = new StringBuilder(); 21 String s=null; 22 //只有当客户端关闭它的输出流的时候,服务端才能取得结尾的-1 23 while ((len = inputStream.read(bytes)) != -1) { 24 // 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8 25 // s.append(new String(bytes, 0, len, "UTF-8")); 26 s= new String(bytes, 0, len,"utf-8"); 27 } 28 System.out.println("server : I get your message : " + s); 29 30 OutputStream outputStream = socket.getOutputStream(); 31 outputStream.write("this is server ".getBytes("UTF-8")); 32 33 inputStream.close(); 34 outputStream.close(); 35 socket.close(); 36 server.close(); 37 } 38 }
创建TCP客户端
1.创建一个Socket对象。
2.从Socket中获取I/O流。
3.对I/O流进行读写操作,完成与服务端的交互。
4.关闭I/O流和Socket
1 import java.io.InputStream; 2 import java.io.OutputStream; 3 import java.net.Socket; 4 5 public class ClientTest { 6 public static void main(String args[]) throws Exception { 7 // 要连接的服务端IP地址和端口 8 String host = "127.0.0.1"; 9 int port = 55533; 10 // 与服务端建立连接 11 Socket socket = new Socket(host, port); 12 // 建立连接后获得输出流 13 OutputStream outputStream = socket.getOutputStream(); 14 String message = "client: hello this is client"; 15 socket.getOutputStream().write(message.getBytes("UTF-8")); 16 //通过shutdownOutput告诉服务器已经发送完数据,后续只能接受数据 17 socket.shutdownOutput(); 18 19 InputStream inputStream = socket.getInputStream(); 20 byte[] bytes = new byte[1024]; 21 int len; 22 StringBuilder sb = new StringBuilder(); 23 while ((len = inputStream.read(bytes)) != -1) { 24 //注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8 25 sb.append(new String(bytes, 0, len,"UTF-8")); 26 } 27 System.out.println("client : I get your message " + sb); 28 29 inputStream.close(); 30 outputStream.close(); 31 socket.close(); 32 } 33 }
在传输过程中,客户端需要给服务端发送消息告知自己发送完成,否则服务端会一直等待,直到超时。
1、 此时需要调用方法告诉服务端,自己发送完成。
socket.shutdownOutput(); 而不是 outputStream.close();
如果关闭输出流那模相应的Socket也关闭,相当于 socket.close();
调用shutdownOutput() ,底层会告知服务端我这边已经写完,服务端知道消息已经读取完,如果服务端有要发送的消息,会发送,如果没有直接关闭socket。
这样会使得,不能再次发送消息,如果发送需要再次建立连接。在访问频率较高时,将急需优化。
2、通过约定符号
双方约定一个短语或字符来当做发送完成的标识,比如约定 end
1 Socket socket = server.accept(); 2 // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取 3 BufferedReader read=new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8")); 4 String line; 5 StringBuilder sb = new StringBuilder(); 6 while ((line = read.readLine()) != null && "end".equals(line)) { 7 //注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8 8 sb.append(line); 9 }
优点:不需要关闭流,当发送完一条消息可以再次发送
缺点:额外的结束标志占带宽,容易误判误被结束,
3、指定长度
现指定命令长度,然后读取指定长度的内容
现在首要的问题就是用几个字节指定长度呢,我们可以算一算:
- 1个字节:最大256,表示256B
- 2个字节:最大65536,表示64K
- 3个字节:最大16777216,表示16M
- 4个字节:最大4294967296,表示4G
当然我们没必要使用最大长度,而使用边长方式来表示长度:
- 第一个字节首位为0:即0XXXXXXX,表示长度就一个字节,最大128,表示128B
- 第一个字节首位为110,那么附带后面一个字节表示长度:即110XXXXX 10XXXXXX,最大2048,表示2K
- 第一个字节首位为1110,那么附带后面二个字节表示长度:即110XXXXX 10XXXXXX 10XXXXXX,最大131072,表示128K
- 依次类推
- 上面提到的这种用法适合高富帅的程序员使用,一般呢,如果用作命名发送,两个字节就够了,如果还不放心4个字节基本就能满足你的所有要求
服务端程序:
1 import java.io.InputStream; 2 import java.net.ServerSocket; 3 import java.net.Socket; 4 5 public class SocketServer { 6 public static void main(String[] args) throws Exception { 7 // 监听指定的端口 8 int port = 55533; 9 ServerSocket server = new ServerSocket(port); 10 11 // server将一直等待连接的到来 12 System.out.println("server将一直等待连接的到来"); 13 Socket socket = server.accept(); 14 // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取 15 InputStream inputStream = socket.getInputStream(); 16 byte[] bytes; 17 // 因为可以复用Socket且能判断长度,所以可以一个Socket用到底 18 while (true) { 19 // 首先读取两个字节表示的长度 20 int first = inputStream.read(); 21 //如果读取的值为-1 说明到了流的末尾,Socket已经被关闭了,此时将不能再去读取 22 if(first==-1){ 23 break; 24 } 25 int second = inputStream.read(); 26 int length = (first << 8) + second; 27 // 然后构造一个指定长的byte数组 28 bytes = new byte[length]; 29 // 然后读取指定长度的消息即可 30 inputStream.read(bytes); 31 System.out.println("get message from client: " + new String(bytes, "UTF-8")); 32 } 33 inputStream.close(); 34 socket.close(); 35 server.close(); 36 } 37 }
先读取两个字节的长度,然后读取消息,客户端程序:
1 import java.io.OutputStream; 2 import java.net.Socket; 3 4 public class SocketClient { 5 public static void main(String args[]) throws Exception { 6 // 要连接的服务端IP地址和端口 7 String host = "127.0.0.1"; 8 int port = 55533; 9 // 与服务端建立连接 10 Socket socket = new Socket(host, port); 11 // 建立连接后获得输出流 12 OutputStream outputStream = socket.getOutputStream(); 13 String message = "the first message!"; 14 //首先需要计算得知消息的长度 15 byte[] sendBytes = message.getBytes("UTF-8"); 16 //然后将消息的长度优先发送出去 17 outputStream.write(sendBytes.length >>8); 18 outputStream.write(sendBytes.length); 19 //然后将消息再次发送出去 20 outputStream.write(sendBytes); 21 outputStream.flush(); 22 //==========此处重复发送一次,实际项目中为多个命名,此处只为展示用法 23 message = "the second message!"; 24 sendBytes = message.getBytes("UTF-8"); 25 outputStream.write(sendBytes.length >>8); 26 outputStream.write(sendBytes.length); 27 outputStream.write(sendBytes); 28 outputStream.flush(); 29 //==========此处重复发送一次,实际项目中为多个命名,此处只为展示用法 30 message = "the third message!"; 31 sendBytes = message.getBytes("UTF-8"); 32 outputStream.write(sendBytes.length >>8); 33 outputStream.write(sendBytes.length); 34 outputStream.write(sendBytes); 35 36 outputStream.close(); 37 socket.close(); 38 } 39 }
运用线程池处理并发:
服务端:
1 import java.io.InputStream; 2 import java.net.ServerSocket; 3 import java.net.Socket; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 7 public class SocketServer { 8 public static void main(String args[]) throws Exception { 9 // 监听指定的端口 10 int port = 55533; 11 //建立服务端 12 ServerSocket server = new ServerSocket(port); 13 // server将一直等待连接的到来 14 System.out.println("server将一直等待连接的到来"); 15 16 //如果使用多线程,那就需要线程池,防止并发过高时创建过多线程耗尽资源 17 ExecutorService threadPool = Executors.newFixedThreadPool(100); 18 19 while (true) { 20 //建立会话 21 Socket socket = server.accept(); 22 23 // Runnable runnable=new Runnable() { 24 // @Override 25 // public void run() { 26 // } 27 // }; 28 // java8 lambda 用于简化匿名类 29 Runnable runnable=()->{ 30 try { 31 // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取 32 InputStream inputStream = socket.getInputStream(); 33 byte[] bytes = new byte[1024]; 34 int len; 35 StringBuilder sb = new StringBuilder(); 36 while ((len = inputStream.read(bytes)) != -1) { 37 // 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8 38 sb.append(new String(bytes, 0, len, "UTF-8")); 39 } 40 System.out.println("get message from client: " + sb); 41 inputStream.close(); 42 socket.close(); 43 } catch (Exception e) { 44 e.printStackTrace(); 45 } 46 }; 47 threadPool.submit(runnable); 48 } 49 50 } 51 }