网络编程套接字(socket api)
了解了网络的一些概念,接下来就要进行网络中的跨主机通信,了解网络中的一些API,这里谈到的API都是针对传输层进行的,这是因为我们编写的代码是在应用层,而传输层就是到了计算机内核中操作系统中已经实现好了的,编写网络编程代码就是调用系统API,将数据传输到传输层,传输层->物理层就由操作系统进行。
将应用层数据传输到传输层的这些API就叫做socket api(网络编程套接字),要将数据传输到传输层就要了解传输层中涉及到的协议:TCP,UDP,这两个协议涉及到两组不同的socket api,要分别进行讨论。
TCP:有连接,可靠传输,面向字节流,全双工。
UDP:无连接,不可靠传输,面向数据包,全双工。
连接:有无连接就是通信双方是否保存对方信息。
可靠/不可靠传输:可靠传输就是会在发出数据报后尽可能的保证能到达对端,不可靠就是在发出数据包后不管了。
全双工:一个信道,可以双向传输。
UDP socket
DatagramSocket
网卡:网卡等硬件设备就是通过文件进行封装的,通过网络发送数据,就是往网卡里接收数据;通过网络接收信息,就是往网卡里读取数据,封装网卡的文件,就是socket文件。
而创建一个DatagramSocket对象就是在操作系统中创建一个socket文件,通过这个对象写入数据,就是通过网卡发送数据;通过对象读取数据,就是通过网卡接收数据。
DatagramSocket中的一些方法receive(接收一个数据报),send(发送一个数据报),close。
DatagramPacket
datagrampacket代表着一个UDP中传输数据的基本单位,也就是一个数据报。
编写一个简单的网络程序
一个网络程序一般是由客户端和服务器构成的,这里只考虑一个客户端和一个服务器,并且服务器返回的响应就是请求的回显服务器(echo server)。
客户端(cilent):
1.从控制台读取请求。
2.通过网络将请求发送给服务器。
3.从服务器读取返回的结果。
4.将返回的结果显示到控制台
服务器(server):
1.接收客户端发来的请求。
2.根据请求处理响应。
3.将响应发送回客户端。
代码实现
服务器编写思路:
首先要创建一个socket文件对象(DatagramSocket),接下来就要通过网卡进行接收请求,那么就要创建一个数据报对象DatagramPacket,将网卡里的数据读写到数据报中,再对数据报中的进行内容进行解析。
public class Server {private DatagramSocket datagramSocket = null;public Server(int port) throws SocketException {//这里添加的port指的是服务器的端口号,源端口号和目的端口号是相对的//要指定的是空闲的端口,如果指定已经使用过的端口,就会抛出异常datagramSocket = new DatagramSocket(port);}public void start() throws IOException {//启动服务器//1.接送客户端请求//1.1 创建一个空的数据报DatagramPacket datagramPacket = new DatagramPacket(new byte[4069],4069);//1.2 将请求读到一个空的数据报中,receive方法会将网卡内容读到数据报中,当没读到东西时就会阻塞datagramSocket.receive(datagramPacket);//1.3 将数据报中二进制的内容转换成字符串String request = new String(datagramPacket.getData(),0,datagramPacket.getLength());//2.根据请求处理响应String torequest = prcess(request);//3.将响应返回给客户端//3.1 将响应构造成数据报,但是UDP是无连接,所以需要将客户端的信息保存到数据报中DatagramPacket datagramPacket1 = new DatagramPacket(torequest.getBytes(),torequest.getBytes().length,datagramPacket.getSocketAddress());datagramSocket.send(datagramPacket1);}private String prcess(String request) {return request;}}
客户端实现思路:
客户端不用知道自己的端口,需要知道服务器的IP和端口。再将输入的内容打包成一个数据报,发送给服务器,再将响应的数据报解析成字符串,最后打印。
public class Cilent {private String ServerIP;private int ServerPort;private DatagramSocket datagramSocket = null;public Cilent(String serverIP, int serverPort) throws SocketException {ServerIP = serverIP;ServerPort = serverPort;datagramSocket = new DatagramSocket();}public void start() throws IOException {Scanner scanner = new Scanner(System.in);//1. 从控制台上读取请求String re = scanner.next();//2. 将请求发送给服务器DatagramPacket datagramPacket = new DatagramPacket(re.getBytes(),re.getBytes().length,InetAddress.getByName(ServerIP),ServerPort);datagramSocket.send(datagramPacket);//3. 读取服务器返回的响应DatagramPacket datagramPacket1 = new DatagramPacket(new byte[4096],4096);datagramSocket.receive(datagramPacket1);String s = new String(datagramPacket1.getData(),0,datagramPacket1.getLength());//4. 将响应显示到控制台System.out.println(s);}
}
客户端和服务器代码的区别:
1.客户端不用知道自己的端口,操作系统会随机分配一个空闲的端口,是因为客户端是发送请求的一方,要知道服务器的IP和端口才能发送,而服务器只要知道自己的端口,返回响应时接收的数据报里有客户端的信息。
TCP协议
两个核心的类:ServerSocket和Socket。
ServerSocket:
打开它相当于打开一个socket文件,但是它不是负责“发送”“接收”,而是建立TCP连接,这个对象是专门给服务器使用的。
Socket:
打开它相当于打开socket文件,这个类双方都会使用,用来发送接收数据。
使用TCP协议编写一个简单网络程序
客户端:
1.用户从控制台输入
2.将请求发送给服务器
3.接送服务器返回的响应
4.将响应返回到控制台
服务器:
1.接收客户端的请求
2.根据请求次数处理响应
3.返回响应
编写服务器思路:TCP协议是要进行连接的,所以不可以一上来就进行读写数据,要先进行从内核中将连接拿取,连接后就会返回一个Socket文件,之后就是对socket文件进行处理。
要拿到Socket中的流对象进行读写操作,读出数据后就要进行处理相应(这里读数据可以用scanner从流对象中进行读取,当读到的数据为空时就返回跳出循环,断开连接),处理完成后就要通过PrintWriter返回响应。
public class server {private ServerSocket serverSocket = null;public server(int port) throws IOException {this.serverSocket = new ServerSocket(port);}private void start() throws IOException {System.out.println("服务器启动");while(true){//通过accent建立连接//ServerSocket是建立连接,Socket负责进行数据通信,后面的都是针对Socket进行数据的传输//Serversocket 和 Socket 之间的关系类似前者将你介绍到总部的销售,后者是给你介绍的销售Socket socket = serverSocket.accept();processa(socket);}}private void processa(Socket socket) {try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()){Scanner scanner = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while (true){//循环处理,一个客户端可能和服务器有多轮响应//读取并解析响应if(!scanner.hasNext()){break;}String request = scanner.next();//根据请求计算响应String response = process(request);//返回响应printWriter.print(response);}}catch (IOException e){e.printStackTrace();}}private String process(String response){return response;}
}
注意事项:一个端口被同时使用于TCP和UDP协议是不会发生冲突的。
编写客户端思路:
TCP是有连接,所以要一上来就进行连接,然后就是进行发送和接收,但是发送时要注意有缓冲区(buffer),发送的printin其实并没有进行发送,而是把数据填写到缓冲区中,这是因为CPU读写内存比外设要快的多,所以在读写入网卡时就会等缓冲区的达到一定时,才会打包发送,但是可以通过flush进行提前唤醒。
public class EcohCilent {public Socket socket = null;public EcohCilent(String serverip, int serverPort) throws IOException {//有连接,先上来要建立连接,是由操作系统进行操作的,但是我们要指定服务器的端口和ipsocket = new Socket(serverip, serverPort);}public void start() {Scanner scanner = new Scanner(System.in);System.out.println("客户端启动!");try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {//接收服务器时创建的scanned,用于读取服务器返回的内容while (true) {Scanner scanner1 = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);//输入请求System.out.println(">");String request = scanner.next();//进行打包发送到服务器writer.println(request);writer.flush();//接收返回的请求if (!scanner1.hasNext()) {System.out.println("服务器断开连接");break;}String rerequest = scanner1.next();System.out.println(rerequest);}}catch(IOException e){e.printStackTrace();}}
注意事项:
1.在TCP客户端中要频繁创建Socket,可能就会导致文件资源泄漏,所以要及时关闭。而UDP中不用进行关闭是因为它不需要频繁创建socket文件。
2.在TCP中当有多个客户端发送请求时就会出现问题。这是因为上面的代码有两层循环,当进入第二层循环时会发生阻塞,这是就要通过线程的方式处理。