1:网络编程基础概念
1.1 网络编程:指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输), 只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据, 也属于网络编程。
1.2 发送端 接收端
数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。 数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。 发送端和接收端两端,也简称为收发端。

1.3 客户端 服务端
客户端:获取服务的一方进程,称为客户端
服务端:常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。
注:服务端提供服务 客户端接收服务


Socket套接字
1.1 定义:由系统提供用于网络通信的技术,基于TCP/IP协议的网络通信基本操作单元,基于Socket套接字的网络程序开发。
1.2 套接字有三种
1.2.1 流套接字 :使用传输层TCP协议
特点 ①有连接
②可靠传输
③面向字节流
⑤有缓冲区(接收区 缓冲区)
对于字节流而言,传输数据为基于IO流,流式数据就是IO流没有关闭得情况下,是无边界数据,可以多次发送,也可多次接收。
1.2.2 数据报套接字 :使用传输层UDP协议
特点 ①无连接
②不可靠传输
③面向数据报
⑤有接收缓冲区 无缓冲区
对于数据报来说,传输数据是一块一块的,发送一段数据必须要分割成多个数据包进行发送。
1.2.3 原始套接字
用于自定义传输层协议,用于读写内核没有处理的的IP协议数。
Java 数据报套接字通信模型
主要使用 udp协议通信,基于DatagramSocket 类创建数据报套接字,使用 DatagramSocket 作为发送或接收udp数据报。

1

Socket注意事项
1. 客户端和服务端:开发时,经常是基于一个主机开启两个进程作为客户端和服务端,但真实的场 景,一般都是不同主机。 2. 注意目的IP和目的端口号,标识了一次数据传输时要发送数据的终点主机和进程 3. Socket编程我们是使用流套接字和数据报套接字,基于传输层的TCP或UDP协议,但应用层协议, 也需要考虑,这块我们在后续来说明如何设计应用层协议。
4. 关于端口被占用的问题
如果一个进程A已经绑定了一个端口,再启动一个进程B绑定该端口,就会报错,这种情况也叫端 口被占用。对于java进程来说,端口被占用的常见报错信息如下:

此时需要检查进程B绑定的是哪个端口,再查看该端口被哪个进程占用。以下为通过端口号查进程 的方式: 在cmd输入 netstat -ano | findstr 端口号 ,则可以显示对应进程的pid。如以下命令显 示了8888进程的pid

在任务管理器中,通过pid查找进程

解决端口被占用的问题: 如果占用端口的进程A不需要运行,就可以关闭A后,再启动需要绑定该端口的进程B 如果需要运行A进程,则可以修改进程B的绑定端口,换为其他没有使用的端口。
UDP 套接字具体方法
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。 DatagramSocket 构造方法:
方法 | 方法说明 |
DatagramSocket() | 创建一udp数据报套接字的socket,绑到本机任意端口(一般用于客户端) |
DatagramSocket(int port) | 创建一个udp数据报套接字的socket,绑定到本机指定端口(用于服务端) |
方法 | 方法说明 |
void receive (DatagramPacket p) | 从套接字接收数据报 如果没有接收到,会阻塞等待 |
void send (DatagramPacket p) | 从套接字发送数据包 不会阻塞等待,直接发送 |
void close() | 关闭数据报套接字 |
DatagramPacket是UDP Socket发送和接收的数据报。 DatagramPacket 构造方法:
方法 | 方法说明 |
DatagramPacket (byte[] bu, int length) | 构造一个DatagramPacket用来接收数据报,接收数据1保存在数组,指定长度。 |
DatagramPacket (byte[] bu , int offset,int length,SocketAddress address) | 构造DatagramPavketPacket 用来发送数据报,发送的数据为字节数组 |
方法 | 方法说明 |
InetAddress getAddress() | 从接受的数据报中,获取发送端主机IP地址,或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接受的数据报中,获取发送端的端口号; 或从发送数据报中,获取接收端主机端口号 |
byte[] getData() | 获取数据报中数据 |
public class UdpServer{
private static voisd main(String[] args) throws IOException{
创建服务端 指定接口端
DatagramPacket socket = new DatagramPacket(port);
while(true){
byte[] be =new byte[4096];
创建数据报,用于接收客户端发送的数据
DatagramPacket da = new DatagramPacket(be, be.length);
socket.receive(da);
因为此时da的数据类型为数据报 要转换为String类型
String r1 = new String(da.getData(),0,da.getLength());
String r2 = process(r1);
DatagramPacket da1 = new DatagramPacket(r2.getBytes(),r2.getBytes().length());
socket.send(da1);

Tcp套接字编程
ServerSocket 是创建TCP服务端Socket的API。 ServerSocket 构造方法:
方法 | 方法说明 |
ServerSocket (int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
方法 | 方法说明 |
socket accept() | 有客户连接后,返回服务端Socket对象,并基于该socket建立与客户端连接,否则阻塞等待。 |
void close() | 关闭套接字 |
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。 不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据 的。 Socket 构造方法:
方法 | 方法说明 |
Socket(String host, int port) | 创建一个客户端流套接字Socket,并于对应IP主机上,对应端口的进程建立连接 |
方法 | 方法说明 |
InetAddress getInetAddress() | 返回套接字连接的地址 |
InputStream getInputStream() | 返回套接字的输入流 |
OutputStream getOutputStream() | 返回套接字的输出流 |
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接: 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数 据。 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以 多次收发数据。 对比以上长短连接,两者区别如下: 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要 第一次建立连接,之后的请 求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时 的,长连接效率更高。 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送 请求,也可以是服务端主动发。 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于 客户端与服务端通信频繁的场景,如聊天室,实时游戏等。 扩展了解: 基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的 消耗是不能承受的。 由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行。 一次阻塞等待对应着一次请求、响应,不停处理也就是长连接的特性:一直不关闭连接,不停的 处理请求。 实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升 。
public class TcpServer{
private static final int port = 8888;
public static void main(String [] args) throws IOException{
ServerSocket srever = new ServerSocket(port);
while(true){
Socket client = server.accept();
InputStream is = client.getInputStream();
方便获取字符串内容,可以将以上字节流包装为字符流
BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8");
一直读取到流结束:TCP是基于流的数据传输,一定要客户端关闭Socket输出流才表示服
务端接收IO输入流结束
while((line = br.readLine()) != null){
System.out.println(line);
}
双方关闭连接:服务端是关闭客户端socket连接
client.close():
}
运行后,服务端就启动了,控制台输出如下:

public class Tcplient{
private static final String SERVER_HOST = "localhost";
private static void main(String[] args) throws IOException{
创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接
Socket client = new Socket(SERVER_HOST, SERVER_PORT);
. 发送TCP数据,是通过socket中的输出流进行发送
OutputStream o = client.getOutputStream();
为了方便输出字符串作为发送的内容,可以将以上字节流包装为字符流
PrintWriter p = new Printwriter(new OutputStreamwriter(o,"UTF_8"));
p.println("hello world");
. 有缓冲区的IO操作,真正传输数据,需要刷新缓冲区
p.fiush();
client.close();
}
}
客户端启动后会发送一个"hello world!" 的字符串到服务端,在服务端接收后,控制台输出内容如下:

以上客户端与服务端建立的为短连接,每次客户端发送了TCP报文,及服务端接收了TCP报文后,双方 都会关闭连接
协议
以上我们实现的UDP和TCP数据传输,除了UDP和TCP协议外,程序还存在应用层自定义协议,可想 想分别都是什么样的协议格式。 对于客户端及服务端应用程序来说,请求和响应,需要约定一致的数据格式: ①客户端发送请求和服务端解析请求要使用相同的数据格式。 ②服务端返回响应和客户端解析响应也要使用相同的数据格式。 请求格式和响应格式可以相同,也可以不同。 约定相同的数据格式,主要目的是为了让接收端在解析的时候明确如何解析数据中的各个字段。 可以使用知名协议(广泛使用的协议格式),如果想自己约定数据格式,就属于自定义协议。
封装/分用 序列化/反序列化
在网络数据传输中,发送端应用程序,发送数据时的数据转换,即对发送数据时的数据包装动作
·使用知名协议,为封装
· 使用小众协议,称为序列化,将程序中的对象转换为特定的数据格式。
接收端应用程序,接受数据转换时,即对接收数据时的数据解析动作
·使用知名协议 为分用
· 使用小众协议,称为反序列化,一般基于接收数据待定的格式,转换为程序的对象。