
网络编程套接字文章目录网络编程套接字前言一、协议1.连接2.可靠传输3.可靠传输4.全双工二、UDP数据报套接字编程1.DatagramSocket2.DatagramPacket3.代码编写总结前言网络编程写代码实现跨主机之间的通信套接字(socket是网络编程的函数统称应用层把数据传输到传输层(操作系统需要调用传输层的函数传输层给应用层提供的编程接口这一组统成为socket也就是网络编程套接字一、协议传输层涉及到两组最主要的协议TCP协议UDP协议特性差别行很大代码编写差异大提供了两组socket函数TCP协议 有连接可靠传输面向字节流全双工UDP协议 无连接不可靠传输面向数据报全双工1.连接连接网络连接抽象的概念(不是物理上的通信双方的两个主机互相保存了对方的核心信息(IP端口断开连接就是把对方的关键信息删掉了TCP在正式通信之前就需要让通信双方保存对方的关键信息在通信完毕之后在删除对方的关键信息 像打电话UDP来说不需要直接上来就发送 像发短信2.可靠传输可靠传输不可靠传输网络通信过程中可能会出现丢包的客观现象就像是车流量太大堵车数据不堵直接丢包3.可靠传输可靠传输就是在对抗丢包尽可能的把数据报传输给对方 就算不能百分百保证送达也能知道对方是否接收到了数据UDP不考虑丢不丢包可靠传输固然有好处但也会付出代价(传输效率4.全双工全双工一个通信可以进行双向传输半双工一个通信只能单项传输二、UDP数据报套接字编程socket有多重含义网络编程套接字socket网络编程中用到的传输层API的统称UDP提供的API主要有两个函数1.DatagramSocketDatagramSocket这是一个socket对象相当于对网卡硬件设备的抽象后续操作这个socket对象就能够操作网卡很多方法⽅法签名 ⽅法说明DatagramSocket()创建⼀个UDP数据报套接字的Socket绑定到本机任意⼀个随机端⼝⼀般⽤于客⼾端DatagramSocket(int port) 创建⼀个UDP数据报套接字的Socket绑定到本机指定的端⼝⼀般⽤于服务端, port就代表端口号DatagramSocket ⽅法⽅法签名 ⽅法说明void receive(DatagramPacket p) 从此套接字接收数据报如果没有接收到数据报该⽅法会阻塞等待void send(DatagramPacket p) 从此套接字发送数据报包不会阻塞等待直接发送void close() 关闭此数据报套接字2.DatagramPacketDatagramPacketDatagramPacket 构造⽅法⽅法签名 ⽅法说明DatagramPacket(byte[] buf, int length) 构造⼀个DatagramPacket以⽤来接收数据报接收的数据保存在字节数组第⼀个参数buf中接收指定⻓度第⼆个参数lengthDatagramPacket(byte[] buf, int offset, int length,SocketAddress address)构造⼀个DatagramPacket以⽤来发送数据报发送的数据为字节数组第⼀个参数buf中从0到指定⻓度第⼆个参数length。address指定⽬的主机的IP和端⼝号DatagramPacket (一次传输的基本单位)⽅法⽅法签名 ⽅法说明InetAddress getAddress() 从接收的数据报中获取发送端主机IP地址或从发送的数据报中获取接收端主机IP地址int getPort() 从接收的数据报中获取发送端主机的端⼝号或从发送的数据报中获取接收端主机端⼝号byte[] getData() 获取数据报中的数据构造UDP发送的数据报时需要传⼊ SocketAddress 该对象可以使⽤ InetSocketAddress来创建。InetSocketAddressInetSocketAddress SocketAddress 的⼦类 构造⽅法⽅法签名 ⽅法说明InetSocketAddress(InetAddress addr, int port) 创建⼀个Socket地址包含IP地址和端⼝号就是UDP数据传输中的基本单位接下来使用它编写UDP服务器UDP客户端也就是回显示服务器相到于网络编程中的hello world3.代码编写服务器public class UdpEchoServer { //先创建一个socket 对象 private DatagramSocket socket null; public UdpEchoServer(int port) throws SocketException { socket new DatagramSocket(port); //port就是端口号 , 此处需要端口号 , 作为构造方法的参数 //端口号区分一个主机上的不同应用程序 //对于服务器来说 , 需要程序员手动设置一个端口号 } public void start() throws IOException { System.out.print(启动服务端...); //这是编写服务器常见做法 ,循环不停处理客户端发来的请求 while (true) { //1.读取请求并解析 , packet是receive的输出型参数 DatagramPacket packet new DatagramPacket(new byte[1024], 1024); socket.receive(packet); //二进制转成字符串,getData可以拿到内置的字节数组 String request new String(packet.getData(), 0, packet.getLength()); //2.根据请求计算相应 , 此处是回显服务器 , 没有计算过程 String response process(request); //3.把相应返回给客户端 //不用.size是因为单位是字符 , 不是字节 DatagramPacket data new DatagramPacket(response.getBytes(), response.getBytes().length, packet.getSocketAddress()); socket.send(data); //4.打印日志 System.out.printf([%s:%d,request:%s,response:%s]\n,packet.getAddress().toString() ,packet.getPort(),request,response); } } //根据请求计算响应 public String process(String request) { return request; } public static void main(String[] args) throws IOException { UdpEchoServer sernew UdpEchoServer(8886); ser.start(); } }服务器 :被动接收请求的一方客户端 : 主动发起请求的一方客户端要想发起请求要提前知道服务器在哪 ,IP 那台机器上端口号 那个程序上要想客户端知道服务器的上述请求需要服务器把两个信息提前确定好服务器的IP好确定 ,服务器的进程运行在哪个机器上服务器的端口号 ,需要编写服务器的程序员指定端口号是2字节的整数取值范围是0-65535只要确保你的端口不和其他进程的端口冲突即可IP不用设置 , 那个机上运行 , IP就是谁的请求(request) :特指客户端给服务器发的数据响应(response/reply)特指服务器给客户端返回的数据receive能够读取到的前提是确实受到了客户端没有发来数据 , receive就会阻塞传入参数表示一个UDP数据包传输的一次基本单位packet.getSocketAddress()UDP是无连接的UDP协议层面上 , 是不会保存对端的信息的因此我们们发送的时候需要把要发给谁这样的信息设置进去此处就是要设置数据要发给谁它是从前面接收的时候获取的 ,放到了前面packet数据包里面socket这里不用关闭,因为这个对象生命周期应该是跟随整个进程的进程结束,PCB(进程控制块)统一释放 , 不用担心泄露问题客户端端口号不需要和服务器端口号一样跑通了 , 但我们追求的是跨主机如果服务器运行在我的电脑上 , 客户端运行在你的电脑上 , 那你们的客户端能访问我的服务器吗?只能我们在一个局域网的时候那咋整?云服务器可以厂商会提供机器把机器租给我们使用它具有外网 IP在哪里都能连上我们现在想要完善这个功能 , 输入汉语 , 输出英语process方法改一下一个端口号只能被一个进程关联有没有办法查看哪些端口号被使用呢netstat -ano 这样儿命令public class UdpDictServer extends UdpEchoServer{ MapString,String mapnew HashMap(); public UdpDictServer(int port) throws SocketException { super(port); map.put(hello,你好); map.put(haha,哈哈); } public String process(String request){ return map.getOrDefault(request,没有查找到); } public static void main(String[] args) throws IOException { UdpDictServer udpnew UdpDictServer(3333); udp.start(); } }TCP也是两个核心类ServerSocket 给服务器使用就像是销售拉客的Socket 既会给客户端用 , 也会给服务器用针对一个客户端 , 提供具体的服务 , 销售]对接的没有xxxpacket这样的类表示传输的基本单位TCP传输的基本单位 , 叫做字节(byte)TCP有链接具体建立连接的流程 , 是操作系统内核实现的 , 代码体现不出来客户端new Socket对象 , 触发TCP建立连接流程 , 服务器这边不做处理 , 就能配合客户端把链接处理完但建立好的连接需要拿到应用程序中,用acceptTCP有两种模型 ,1.短链接 :一个连接 , 只发送一次请求响应(不常用 , 建立连接 , 断开连接都是开销的2.长连接:一个连接 , 会进行多次请求响应对于TCP , 端口IP就在socket里面存着 , 而UDP的不是在DatagramSocket中 , 是在DatagramPaketpublicclassTcpEchoClient{privateSocketsocketnull;publicTcpEchoClient(Stringaddress,intport)throwsIOException{socketnewSocket(address,port);}publicvoidstart(){System.out.println(客户端启动);while(true){try(InputStreaminputStreamsocket.getInputStream();OutputStreamoutputStreamsocket.getOutputStream()){ScannerscannernewScanner(System.in);Scannerscanner2newScanner(inputStream);PrintWriterprintWriternewPrintWriter(outputStream);while(true){System.out.println(-);if(!scanner.hasNext()){break;}Stringrequestscanner.next();printWriter.write(request);if(!scanner2.hasNext()){break;}Stringresponsescanner2.next();System.out.println(-response);}}catch(IOExceptione){e.printStackTrace();}}}publicstaticvoidmain(String[]args)throwsIOException{TcpEchoClientclientnewTcpEchoClient(127.0.0.1,9090);client.start();}}为什么没有反应因为printWriter.write(request);进行IO过程 , 属于抵消操作 ,为提高效率 , 只是写入缓冲区 , 满了之后才会释放出来 , 没有真的发送这里我们用flush , 冲刷缓冲区 , 缓冲区没有满也发送服务器那里也要记得加上之前用的read , writer , fileInputaStream/fileOutStream这些都是不带缓冲区的代码UDP的请求响应有缓冲区吗他的缓冲区在操作系统内核里 , 应用程序不涉及 ,TCP在应用程序里涉及到flush是针对应用程序这里的缓冲区进行操作的当前代码中存在资源泄露while(true){//如果客户端和服务器建立好了连接 , 就会返回SocketsocketserverSocket.accept();processConnection(socket);}这里每次连接的时候 , 都会触发一次accept , 都会创建一个socket , 占用一个文件描述表的位置 , 在finally地方放一下其他的生命周期跟随整个进程代码现在我们点击再次运行 , 会发现只是停止旧的在开启新的 ,并不是多个客户端一起运行启动多个客户端怎么弄点击这个长方形点击编辑配置点击修改选项允许多个实例就欧克了现在点击运行客户端 , 会发现运行几个就会有几个客户端 , 但只有第一个可以使用 , 为甚我们把第一个关掉 , 第二个好了 , 第二个关了 , 第三个好了现在看一下UDP服务器试试UDP客户端不会有这个情况TCP代码有问题!!!第一个客户端连接上服务器之后 , 会一直在connect里面打转 , 出不来 , 第二个只能卡在accept上面建立连接了 , 但是是在内核里面在应用程序里面如果不去调用accept , 连接没法拿到应用程序中 , 没法进一步进行这里的读写处理就像是一个服务员多个客人 , 顾此失彼 , 我们多找几个服务员用多线程 , 多个线程分别去处理这些逻辑public void start() throws IOException { System.out.println(开始了); while(true){ //如果客户端和服务器建立好了连接 , 就会返回 Socket socketserverSocket.accept(); //processConnection(socket); Thread threadnew Thread(()-{ try { processConnection(socket); } catch (IOException e) { throw new RuntimeException(e); } }); thread.start(); } }好使了好使了!!!这样做就好了!!!激不激动 , 惊不惊喜 , 意不意外!!!好激动!和之前的知识串上了!!!上述多线程模型服务器是属于多年前常见的服务器实现方式如果客户端进一步增加 .线程数也会很多 , 线程会频繁创建释放 , 虽然轻量 , 但是开销无法忽略用线程池合适但是这个这个对于客户持续时间短这样是合适的 , 但是持续时间长就不合适了 , 同一时刻线程数目会很多 , 线程不是越多越好进一步优化IO多路复用本质上一个线程同时处理多个socket , 一个socket不是一直需要传输数据在等待的过程中 , 处理其他的线程这里不做过多解释在tcp代码中 , 有一个隐含条件 , 请求和响应都是带\n作为结尾的读到空白符就返回 , 因此我们用println , 用print会发现不行不能在控制台输入 \n, scanner.next()会自动把\n去掉Tcp也可以整个字典在日常工作中 , 大概率不会直接使用socket.api后面会接触到更上一层的框架 , 胆汁能解决一部分问题 , 会有一部分专门问题框架不能胜任 , 就需要他了总结