一.为什么要网络编程?
用户在浏览器,打开视频网站时,实际是通过网络获取到的网络资源,所有的网络资源都是通过网络编程进行数据传输的。
二.Socket套接字
1.概念
Socket套接字本质上是一个编程接口,是由传输层给应用层提供的。当俩台设备进行通信时,Socket内部封装了通信的五元组。内核通过五元组唯一标识一条路径,确保数据能够准确送达到指定设备(目的IP)的指定进程(目的端口)。
2.分类
我们主要学习的是流套接字(使用传输层TCP协议)和数据报套接字(使用传输层UDP协议)
1)数据包套接字
特点:无连接,面向数据包,不可靠传输,全双工,有接受缓冲区,无发送缓冲区.一次最多传送64kb(讲UDP的结构可知道)
2)流套接字
**特点:**有连接,面向字节流,可靠传输,全双工,既有接受缓冲区又有发送缓冲区,传输数据大小不限。
3.名词解释
1)有连接和无连接
这里的连接是逻辑上的连接,而不是物理上的连接。在TCP协议中,如果设备A和设备B进行通信就得先建立连接,让A保存B的相关信息,B也保存A的相关信息,彼此之间知道自己连接的是谁。在UDP协议中,是无连接,俩台设备不会保存对端的相关信息,直接根据五元组中的目标IP和端口转发数据包,因此无需建立链路也能通信。
2)可靠传输和不可靠传输
网络上,是非常容易出现数据丢失的情况(丢包)如光信号和电信号容易遭受到外部环境的影响。
可靠传输的意思不是让数据100%到达目的地,而是在出现丢包的情况下能够知道,然后在一定条件下进行重传。而不可靠传输是把数据发出去后就直接不管了
3)面向字节流和面像数据报
面向字节流代表传输数据的基本单位为字节,但会出现粘包问题(讲TCP的结构会讲)
面向数据报代表传输数据的基本单位为数据报,不会出现粘包问题。
4)全双工和半双工
全双工代表双向通信(既能读又能写)
半双工代表单向通信(要么读要么写)
总结: 对于 TCP 而言,通信前需要通过三次握手(TCP协议会讲)在内核中建立一条逻辑链路 (即维持双方的状态同步),以保证可靠传输;而对于 UDP,内核不维护对端状态,直接根据五元组中的目标地址转发数据包,因此无需建立链路也能通信。
三.网络编程
1.UDP数据报套接字编程
API介绍
1)DatagramSocket
是UDP Socket,用于发送和接受UDP数据包
构造方法:

普通方法:

2)DatagramPacket
是UDP Socket发送和接受的数据报
构造方式:

普通方法:

使用:

代码:
java
import java.io.IOException;
import java.net.*;
public class UDPEchoServer {
private DatagramSocket socket =null;
//指定ip
public UDPEchoServer(String ip, int port)
throws UnknownHostException, SocketException {
socket =new DatagramSocket(port,InetAddress.getByName(ip));
}
//不指定ip,代表绑定本机所有ip
public UDPEchoServer(int port) throws SocketException {
socket =new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("........服务端启动!!!");
// 不断处理请求
while (true){
DatagramPacket requestPacket =new DatagramPacket(new byte[4096],4096);
// 读取请求
socket.receive(requestPacket);
// 处理请求,把字节数组转化为字符串形式,这里得传有效长度,不能传requestPacket.getData().length
String request =new String(requestPacket.getData(),0, requestPacket.getLength());
String response = process(request);
// 响应请求
DatagramPacket responsePacket =new DatagramPacket(
response.getBytes(),response.getBytes().length,//这里传的是字节数,不能传response.length
requestPacket.getAddress(),requestPacket.getPort());
socket.send(responsePacket);
System.out.println("客户端"+requestPacket.getPort()+requestPacket.getAddress()+"发出请求");
System.out.printf("回复:%s\n",response);
}
}
private String process(String request) {
//为回显服务器,不做处理
return request;
}
public static void main(String[] args) throws IOException {
UDPEchoServer echoServer =new UDPEchoServer(9090);
echoServer.start();
}
}
java
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 {
this.serverIp = serverIp;
this.serverPort = serverPort;
//找空闲端口
socket =new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner =new Scanner(System.in);
while (true){
//用户输入请求
String request = scanner.next();
//发送请求
DatagramPacket requestPacket =
new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
// 接收响应
DatagramPacket responsePacket =new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response =new String(responsePacket.getData(),
0,responsePacket.getLength());
System.out.println("response:"+response);
}
}
public static void main(String[] args) throws IOException {
UDPEchoClient udpEchoClient =new UDPEchoClient("10.2.14.62",9090);
udpEchoClient.start();
}
}

2.TCP套接字编程
API介绍
1)ServerSocket
是创建TCP服务端Socket
构造方法:

普通方法:

2)Socket
Socket 是客⼾端Socket,或服务端中接收到客⼾端建⽴连接(accept⽅法)的请求后,返回的服 务端Socket。 不管是客⼾端还是服务端Socket,都是双⽅建⽴连接以后,保存的对端信息,及⽤来与对⽅收发数据 的。
构造方法:

普通方法:

使用:

代码:
java
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.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TCPEchoServer {
private ServerSocket socket =null;
public TCPEchoServer(int port) throws IOException {
socket =new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务端启动");
ExecutorService executorService = Executors.newCachedThreadPool();
//每一个连接处理一个客户端
while (true){
Socket clientSocket = socket.accept();
executorService.submit(()->{
System.out.println("客户端启动:"+
clientSocket.getInetAddress()+" "+clientSocket.getPort());
processConnect(clientSocket);
});
}
// while (true){
// Socket clientSocket = socket.accept();
// Thread thread =new Thread(()->{
// System.out.println("客户端启动:"+
// clientSocket.getInetAddress()+" "+clientSocket.getPort());
// processConnect(clientSocket);
// });
// thread.start();
// }
}
private void processConnect(Socket clientSocket) {
try(InputStream inputStream =clientSocket.getInputStream();
OutputStream outputStream =clientSocket.getOutputStream()){
Scanner scanner =new Scanner(inputStream);
PrintWriter writer =new PrintWriter(outputStream);
while (true){
if(!scanner.hasNext()){
System.out.println("客户端退出:"+
clientSocket.getInetAddress()+" "+clientSocket.getPort());
break;
}
String request =scanner.next();
String response =process(request);
writer.println(response);
writer.flush();
System.out.println("客户端"+clientSocket.getInetAddress()+" "+
clientSocket.getPort()+"发来请求");
System.out.println("回复:"+response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TCPEchoServer server =new TCPEchoServer(9090);
server.start();
}
}
java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TCPEchoClient {
private Socket clientSocket =null;
public TCPEchoClient(String serverIp,int serverPort) throws IOException {
clientSocket =new Socket(serverIp,serverPort);
System.out.println("客户端上线:"+clientSocket.getLocalAddress()+
" "+clientSocket.getLocalPort());
}
public void start(){
try(InputStream inputStream =clientSocket.getInputStream();
OutputStream outputStream =clientSocket.getOutputStream()){
Scanner scannerRead =new Scanner(inputStream);
PrintWriter writer =new PrintWriter(outputStream);
Scanner scannerWrite =new Scanner(System.in);
while (true){
System.out.println("请输入请求---》");
String request =scannerWrite.next();
writer.println(request);
writer.flush();
String response =scannerRead.next();
System.out.println("回复:"+response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TCPEchoClient client =new TCPEchoClient("127.0.0.1",9090);
client.start();
}
}
注意:如果客户端进一步增加,就会产生大量的线程。为了处理这个问题,操作系统内部内置了IO多路复用----本质上是一个线程负责处理多个客户端的请求。