目录
Socket套接字
⽹络编程,指⽹络上的主机,通过不同的进程,以编程的⽅式实现⽹络通信(或称为⽹络数据传 输)。Socket套接字是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
操作系统提供的socket api 不止有一套,而是有好几套。
Socket套接字主要针对传输层协议分为如下三类:
1)流套接字:使用传输层TCP协议。
2)数据报套接字:使用传输层UDP协议。
3)Unix域套接字:不能跨主机通信,只是本地主机上的进程和进程之间的通信方式(现在使用的很少了,我们这里不做讨论)。
流套接字与数据报套接字
TCP和UDP都是传输层协议,都是给应用层提供服务的。但是由于这两个协议的特点,差异非常大,印次我们需要搞两套api来分别表示。
特点:
TCP:有连接,可靠传输,面向字节流,全双工。
UDP:无连接,不可靠传输,面向数据报,全双工。
有连接 VS 无连接
计算机中的连接(Connection) 是一个”抽象的概念“,计算机的连接认为是要建立连接的双方,各自保存对方的信息。此时,就认为是建立了一个”抽象的连接“。有连接就是通信双方保存对方的信息,无连接就是通信双方不需要保存对方的信息。有连接就好比打电话,只有对方接听,两方才能通信。无连接就好比发微信,不需要”先接通“,直接就可以发送。
可靠传输 VS 不可靠传输
可靠传输即将要传输的数据尽可能地传输给对方。TCP内部提供了一系列的机制,来实现可靠传输。(即使”尽可能“也无法保证100%传输,因为网络环境过于复杂,丢包是不能完全避免的)。
UDP则是不可靠传输,传输数据的时候,不关心对方是否收到,发了就完事了。而UDP的优势是传输效率较快。
面向字节流 VS 面向数据报
字节流数据的特征是在IO流没有关闭的时候,是无边界的数据,可以多次发送,也可以分开多次接收,就像水流一样,读写操作非常灵活。而面向数据报,传输的数据是一块一块的,传输数据的基本单位是一个个的UDP数据报,一次读写只能读写一个完整的UDP数据报,不能搞半个,也不能一个半。
全双工 VS 半双工
全双工:一条链路,能够进行双向通信。
半双工:一条链路,只能进行单向通信。
TCP,UDP都是全双工。
因为网线里面都是电信号,从这边进那边出,还是从那边近这边出,都是可以的。
UDP数据报套接字编程
系统中的socket,可以理解为是一种文件,socket文件,就可以视为是“网卡”这种硬件设备的抽象表现形式。针对socket文件的读写操作,就相当于针对网卡这个硬件设备进行读写。比如,你想操控空调,直接去按空调不方便,就可以拿遥控器来操作。具有“遥控器属性”这样的概念,计算机中起了个专门的名字“句柄(handle)”.
java中提供了很多的socket api ,重点是两个类:
1)DatagramSocket
DatagramSocket可以视为是“操作网卡”的遥控器,针对这个对象进行读写操作,就是针对网卡进行读写操作。其中比较关键的方法:
注:socket也是一种文件,用完也要关闭,否则会占着文件描述符表的一个表项。
2)DatagramPacket
针对UDP数据报的一个抽象表示,一个DatagranPacket对象,就相当于一个UDP数据报。一次发送/一次接收,就是传输了一个DatagramPacket对象。
下面我们通过java代码实现客户端与服务端的通信:
服务端代码:
package Web;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
//1.读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
//为了方便在java中处理,可以把上述数据报中的二进制数据构造成String
String request = new String(requestPacket.getData(), 0,requestPacket.getLength());
//2.根据请求计算响应
String response = process(request);
//3.把响应写回到客户端
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,response.getBytes().length,
requestPacket.getSocketAddress() );
socket.send(responsePacket);
//打印ip地址和端口号
System.out.printf("[%s : %d] req = %s , resp = %s\n",requestPacket.getAddress(), requestPacket.getPort(),
request,response);
}
}
private String process(String request) {
//由于回显,请求和响应是一样的
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
注:Echo称为“回显”,正常的服务器,给它发送不同的请求,会返回不同的响应。回显的意思是请求发了啥,响应就是啥,这个过程中,没有计算,也没有业务逻辑。 此处使用回显,是为了帮大家单纯的去认识socket api的用法。
用户端代码:
package Web;
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 {
System.out.println("客户端启动");
Scanner sc = new Scanner(System.in);
while (true){
System.out.print("请输入要发送的请求");
//1.从控制台读取用户输入
String request = sc.next();
//2.构造请求并发送
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
//3.读取响应数据
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
//4.显示响应到控制台上
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
以上,关于网络编程,希望对你有所帮助。