推荐30个国外优秀的设计教程网站,做关于水果的网站,百度指数的主要用户是,太原做网站兼职基于 Socket 网络编程 前言一、基于Socket的网络通信传输#xff08;传输层#xff09;二、UDP 的数据报套接字编程1、UDP 套接字编程 API2、使用 UDP Socket 实现简单通信 三、TCP 流套接字编程1、TCP 流套接字编程 API2、使用 TCP Socket 实现简单通信3、使用 Tcp 协议进行… 基于 Socket 网络编程 前言一、基于Socket的网络通信传输传输层二、UDP 的数据报套接字编程1、UDP 套接字编程 API2、使用 UDP Socket 实现简单通信 三、TCP 流套接字编程1、TCP 流套接字编程 API2、使用 TCP Socket 实现简单通信3、使用 Tcp 协议进行网络传输的“五大要点” 前言
我们再进行网络编程时主要是编写“应用层”代码如果真正要发送这个数据需要上层协议调用下层协议也就是应用层调用传输层传输层给应用层提供一组 API统称为 Socket API
一、基于Socket的网络通信传输传输层
Socket 套接字是由系统提供用于网络通信的技术是基于 TCP/IP 协议的网络通信的基本操作单元。基于Socket 套接字的网络程序开发就是网络编程。
在这一块我们对于传输层协议主要学习两种 Socket 套接字
数据报套接字使用传输层UDP协议。UDP即 User Datagram Protocol用户数据报协议传输层协议。 以下为UDP的特点细节后续介绍 无连接使用 udp 通信的双方不需要刻意保存对端的相关信息。不可靠传输不关注结果面向数据报以一个 udp 数据报为基本单位全双工双向通信有接收缓冲区无发送缓冲区大小受限一次最多传输 64k 流套接字使用传输层TCP协议。TCP即 Transmission Control Protocol传输控制协议传输层协议。 以下为TCP的特点细节后续介绍 有连接使用 TCP 通信双方则需要刻意各自记录了对方的信息可靠传输发送后尽可能的传输过去失败了也知道面向字节流以字节流为传输的基本单位读写方式非常灵活全双工双向通信有接收缓冲区也有发送缓冲区大小不限 二、UDP 的数据报套接字编程
DatagramSocket 是UDP Socket用于发送和接收UDP数据报。 这里的 Socket 我们可以类比于 File 对象理解我们知道如果我们是不能直接操作硬盘的如果想要操作硬盘就需要借助 File。对象间接操作。Socket 也是类似它对应于网卡这个硬件设备如果我们想要操作网卡就需要一个 Socket 对象间接操作网卡。向 socket 对象中1写数据就相当于通过网卡发送消息。从 socket 对象中读数据就相当于通过网卡接收消息。 1、UDP 套接字编程 API
1DatagramSocket DatagramSocket 构造方法
方法签名方法说明DatagramSocket()创建一个UDP数据报套接字的Socket绑定到本机任意一个随机端口一般用于客户端DatagramSocket(int port)创建一个UDP数据报套接字的Socket绑定到本机指定的端口一般用于服务端
说明对于服务器一般要手动指定一个固定的端口客户端则不要求。类似于我去食堂吃饭给我提供食物的窗口就是一个服务器而我是接收食物的消费者可看做是客户端窗口需要有一个固定的窗口号便于我找到它而我在享受食物时没有固定的座位哪里有空位就做哪里。
DatagramSocket 方法
方法签名方法说明void receive(DatagramPacket p)从此套接字接收数据报如果没有接收到数据报该方法会阻塞等待void send(DatagramPacket p)从此套接字发送数据报包不会阻塞等待直接发送void close()关闭此数据报套接字
2DatagramPacket DatagramPacket 是 UDP Socket 发送和接收的数据报
DatagramPacket构造方法
方法签名方法说明DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报接收的数据保存在字节数组第一个参数buf中接收指定长度第二个参数lengthDatagramPacket(byte[] buf, int offset, int length)以用来接收数据报接收的数据保存在字节数组第一个参数buf中接收从offset到指定长度lengthDatagramPacket(byte[] buf, int length, InetAddress address, int port)用来发送数据报发送的数据为字节数组第一个参数buf中length 为数据长度address为目标主机的地址port为目标主机的端口号。DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)用来发送数据报发送的数据为字节数组第一个参数buf中从offset到 length 为数据长度address为目标主机的地址port为目标主机的端口号。DatagramPacket(byte[] buf, int length, SocketAddress address)用来发送数据报发送的数据为字节数组第一个参数buf中length 为数据长度。DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造一个DatagramPacket以用来发送数据报发送的数据为字节数组第一个参数buf中从offset到指定长度length。address指定目的主机的IP和端口号
DatagramPacket方法
方法签名方法说明InetAddress getAddress()从接收的数据报中获取发送端主机IP地址或从发送的数据报中获取接收端主机IP地址int getPort()从接收的数据报中获取发送端主机的端口号或从发送的数据报中获取接收端主机端口号byte[] getData()获取数据报中的数据
2、使用 UDP Socket 实现简单通信
下面我们在 Java 中使用 UDP 协议实现的一个简单的客户端和服务端的通信。
下面的 服务器-客户端 代码看起来挺复杂其实和数据库中的 JDBC 差不多都是固定的套路尽管之后在写更复杂的 服务器-客户端 程序也都是在这个基础上拓展。例如下面的 UDP Socket 通过传输层实现网络通信代码无非就下面几个步骤
1.对于服务器 1读取请求并解析 2根据请求计算响应 3把响应结果发送到客户端 2.对于客户端 1构造并发送请求 2接收服务器返回的响应并解析响应
服务器程序
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;// Echo-回显服务器。客户端发了个请求服务器返回一个一模一样的响应。
public class UdpEchoSever {// 需要先定义一个 socket 对象使用网络通信必须要使用 socekt 对象private DatagramSocket socket null;// 绑定一个端口号不一定能成功比如某个端口号已经被别的进程占用了此时这里的绑定操作就会出错。// 需要注意的是同一个主机上一个端口同一时刻只能被一个进程绑定。public UdpEchoSever(int port) throws SocketException {socket new DatagramSocket(port);}// 启动服务器主逻辑public void start() throws IOException {System.out.println(服务器启动);while (true) {// 每次循环做三件事// 1. 读取请求并解析// 构造一个空的DatagramPacket对象用来接收客户端请求DatagramPacket requestPacket new DatagramPacket(new byte[4096],4096);// 从网卡上接收请求 此处的 requestPacket 为输出型参数socket.receive(requestPacket);// 这里为了方便处理这个请求将数据包转化为 StringString request new String(requestPacket.getData(),0,requestPacket.getLength());// 2. 根据请求计算响应String response process(request);// 3. 把响应结果写回到客户端// 根据 response 字符串构造一个 DatagramPacket// 和请求 packet 不同此处构造响应的时候需要指定这个包要发给谁DatagramPacket responsePacket new DatagramPacket(response.getBytes(),response.getBytes().length,// requestPacket 是从客户端这里收来的getSocketAddress 会得到客户端的 ip 何为端口requestPacket.getSocketAddress());socket.send(responsePacket);// 方面查看打印一下日志// ip 和 端口号 请求内容 响应内容System.out.printf([%s:%d] req: %s, resp: %s\n,requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);}}// process是请求处理方法这是服务器中的一个关键环节public String process(String request) {return request;}// 主函数public static void main(String[] args) throws IOException {UdpEchoSever udpEchoSever new UdpEchoSever(9090);udpEchoSever.start();}
}
客户端程序
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket null;private String serverIP;private int serverPort;// 客户端启动, 需要知道服务器在哪里!!public UdpEchoClient(String serverIP, int serverPort) throws SocketException {// 对于客户端来说, 不需要显示关联端口.// 不代表没有端口, 而是系统自动分配了个空闲的端口.socket new DatagramSocket();this.serverIP serverIP;this.serverPort serverPort;}public void start() throws IOException {// 通过这个客户端可以多次和服务器进行交互.Scanner scanner new Scanner(System.in);while (true) {// 1. 先从控制台, 读取一个字符串过来// 先打印一个提示符, 提示用户要输入内容System.out.print(- );String request scanner.next();// 2. 把字符串构造成 UDP packet, 并进行发送.DatagramPacket requestPacket new DatagramPacket(request.getBytes(), request.getBytes().length,InetAddress.getByName(serverIP), serverPort);socket.send(requestPacket);// 3. 客户端尝试读取服务器返回的响应DatagramPacket responsePacket new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);// 4. 把响应数据转换成 String 显示出来.String response new String(responsePacket.getData(), 0, responsePacket.getLength());System.out.printf(req: %s, resp: %s\n, request, response);}}public static void main(String[] args) throws IOException {// 127.0.0.1 是一个特殊的IP地址表示本机的回环地址。UdpEchoClient udpEchoClient new UdpEchoClient(127.0.0.1, 9090);udpEchoClient.start();}
} 对于UDP Echo Sever 来说socket 对象的生命周期伴随整个程序的不需要 close。这个 socket 对象是出了循环就不用了但是循环结束就意味着 start 结束意味着 main 方法结束意味着进程结束进程结束所有文件资源就自动释放了。 三、TCP 流套接字编程
1、TCP 流套接字编程 API
1SeverSocket ServerSocket 是创建TCP服务端Socket的API。
ServerSocket构造方法
方法签名方法说明ServerSocket(int port)创建一个服务端流套接字Socket并绑定到指定端口
ServerSocket 方法
方法签名方法说明Socket.accept()开始监听指定端口创建时绑定的端口有客户端连接后返回一个服务端Socket对象并基于该Socket建立与客户端的连接否则阻塞等待void close()关闭此套接字
2Socket Socket 是客户端Socket或服务端中接收到客户端建立连接accept方法的请求后返回的服务端Socket。
不管是客户端还是服务端Socket都是双方建立连接以后保存的对端信息及用来与对方收发数据的。
Socket 构造方法
方法签名方法说明Socket(String host, intport)创建一个客户端流套接字Socket并与对应IP的主机上对应端口的进程建立连接
Socket 方法
方法签名方法说明int getPort()返回此套接字连接到的远程端口号InetAddress getInetAddress()返回套接字所连接的地址InputStream getInputStream()返回此套接字的输入流OutputStream getOutputStream()返回此套接字的输出流void close()关闭此套接字
2、使用 TCP Socket 实现简单通信
下面我们同样写一个简单的基于 TCP 协议实现的回显服务器和客户端用户可以通过客户端向服务器发送请求并接收响应。
服务器程序
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoSever {// 这里有个比喻// severSocket 看做是外场拉客的小哥// clientSocket 看做内场服务的小姐姐// severSocket 只有一个clientSocket 会给每个客户端都分配一个private ServerSocket serverSocket null;public TcpEchoSever(int port) throws IOException {serverSocket new ServerSocket(port);}public void start() throws IOException {System.out.println(服务器启动!);ExecutorService pool Executors.newCachedThreadPool();while (true) {Socket clientSocket serverSocket.accept();// 如果直接调用该方法会影响这个循环的二次执行导致 accept 不及时// 创建新线程用新线程调用 processConnection// 每次来一个新的客户端都创建一个新线程// 1.方案一每次创建线程每次创建销毁开销较大
// Thread t new Thread(()-{
// processConnection(clientSocket);
// });
// t.start();// 2.方案二使用线程池pool.submit(()-{try {processConnection((clientSocket));} catch (IOException e) {e.printStackTrace();}});}}private void processConnection(Socket clientSocket) throws IOException {// 打印一下日志System.out.printf([%s:%d] 客户端上线!\n,clientSocket.getInetAddress().toString(),clientSocket.getPort());// try () 这种写法( ) 中允许写多个流对象使用 ; 分割try (InputStream inputStream clientSocket.getInputStream();OutputStream outputStream clientSocket.getOutputStream()) {// 为了简单把字节流包装成了更方便的字符流Scanner scanner new Scanner(inputStream);PrintWriter printWriter new PrintWriter(outputStream);// 一次可能发来多个请求这里规定以 \n 为分隔符while (true) {// 1.读取请求// 特殊处理一下if (!scanner.hasNext()) {// 读取的流到了结尾对端关闭了System.out.printf([%s:%d] 客户端下线!\n,clientSocket.getInetAddress().toString(),clientSocket.getPort());break;}// 直接使用 scanner 读取一段字符串String request scanner.next();// 2.根据请求计算响应String response process(request);// 3.把响应写会给客户端不要忘了响应里也是要带上换行的printWriter.println(response);// 写网卡为全缓冲这里使用flush刷新printWriter.flush();// 最后打印一下日志System.out.printf([%s:%d] req: %s resp: %s\n,clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);}} catch (IOException e) {e.printStackTrace();} finally {// 关闭连接clientSocket.close();}}// 处理请求public String process(String request) {return request;}// 主方法public static void main(String[] args) throws IOException {TcpEchoSever tcpEchoSever new TcpEchoSever(9090);tcpEchoSever.start();}}
客户端程序
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;// idea 中默认一个程序只能启动一个启动多个客户端可配置一下 IDEA。public class TcpEchoClient {private Socket socket null;// ***只有这里会建立连接和 Udp 不同***public TcpEchoClient(String severIp, int port) throws IOException {// 这个操作就相当于让客户端和服务器建立 TCP 连接// 这里的链接连上了accept 就会返回socket new Socket(severIp,port);}public void start() {Scanner scanner new Scanner(System.in);try (InputStream inputStream socket.getInputStream();OutputStream outputStream socket.getOutputStream()) {// 将字节流包装成字符流Scanner scannerFromSocket new Scanner(inputStream);PrintWriter printWriter new PrintWriter(outputStream);while (true) {// 1.从键盘上读取用户输入的内容System.out.print(-);String request scanner.next();// 2.把读取的内容构成请求发给服务器// 注意这里的发送是带换行的printWriter.println(request);// 写网卡为全缓冲这里使用flush刷新printWriter.flush();// 3.从服务器读取响应内容String response scannerFromSocket.next();// 4. 把响应的结果显示到控制台上System.out.printf(req: %s ; resq: %s\n,request,response);}} catch (IOException e) {e.printStackTrace();}}// 主方法public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient new TcpEchoClient(127.0.0.1,9090);tcpEchoClient.start();}}3、使用 Tcp 协议进行网络传输的“五大要点”
1自定义简单的应用层协议 对于客户端及服务端应用程序来说请求和响应需要约定一致的数据格式上述为了简单做了如下简单约定 每个请求是个字符串请求和请求之间使用\n(换行符)分割 由于是回显服务器响应和请求是一模一样的因此也遵循上述规则。
2写网卡是全缓冲写文件也是全缓冲 为了提高IO效率引入了缓冲区使用缓冲区可以减少IO次数提高整体的效率。上述 printWriter.println(“内容”) 过后内容就被写入到了缓冲区如果不刷新缓冲区就要等到缓冲区满自动刷新到网卡中所以执行上述程序可能就会出现只请求不响应的情况为了解决这个问题我们可以在每次写网卡后手动进行刷新printWriter.flush() 3长连接 与 短连接 长连接和短连接是指在网络编程中不同的连接方式。 短连接指客户端与服务器建立连接后在完成一次请求-响应操作之后就会断开连接。每次请求都需要重新建立连接这种方式可以保证连接使用的资源较少但也对服务器的压力较大。常用于小数据量的频繁通信场景例如HTTP协议。 而长连接则是指客户端与服务器建立连接后在一段时间内可以保持连接状态多次请求-响应操作共用这一个连接。这种方式相对于短连接可以减少连接建立、关闭的次数提高了通信效率但是缺点是需要维护连接的状态如果长时间没有交互则需要进行心跳检测等机制来维持连接状态。常用于对实时性要求较高的通信场景例如即时通讯、游戏等。 在上述TCP协议中使用到长连接。 4使用多线程 上述例子的服务器中使用到了多线程如果不使用多线程代码可能产生 BUG。因为上述 start 的 while 循环是用来循环的接收连接而下面的 processConnection 内部也有一个循环用来循环的处理连接。假设现在来了一个连接start 方法接收连接后其中的 processConnection 就开始循环的处理这个连接直到这个连接关闭但是如果这个期间又有别的客户端进行新的连接由于当前start中的第一次循环还没结束就会导致一直阻塞使其他连接处理不及时。为了解决上述问题一个很好的办法就是使用多线程为每个连接都分配一个线程独立处理。 5频繁创建生命周期又短资源的需要 close 及时释放 像上述使用 UDP 协议进行网络通信这种生命周期伴随整个程序的不需要 close。在这里使用 TCP 进行网络通信时服务器那里的每个 Socket 对象只是给一个连接提供服务的可能会有很多个连接。在这种情况下服务器会为每个连接都创建一个新的 Socket 对象作为后续通信的基础。当这个连接不再需要服务时需要将相应的 Socket 对象关闭以便及时释放资源。