当前位置:   article > 正文

网络编程 — socket套接字 — 网络编程_socket套接字编程

socket套接字编程

目录

一、什么是Socket套接字

二、UDP数据包套接字编程

1.DatagramSocket API

(1)关于Socket对象

(2)DatagramSocket方法

2. DatagramPacket API

DatagramPacket方法

3.基于UDP Socket的客户端服务器程序(回显服务器echo server)

4.单词翻译服务器

三、TCP数据包套接字编程

1.SeverSocket API

2.Socket API

3.基于TCP套接字的回显程序

一、什么是Socket套接字

数据报Datagram,通过网络传输的数据的基本单元,包含一个报头(header)和数据本身,其中报头描述了数据的目的地以及和其它数据之间的关系。

概念socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程

TCP/IP五层网络模型:应用层,传输层网络层,数据链路层,物理层。其中应用层主要是应用程序,传输层和网络层是是由系统内核封装的,数据链路层和物理层主要是由硬件和驱动实现的。在网络分层下,数据的传输离不开封装和分用。

程序员写网络程序 ,主要编写的是应用层代码,其他下面四层是程序员无法改变的。当应用程序需要将数据上传,此时就需要上层协议,调用下层协议,应用层调用传输层,传输层给应用层提供一组api,这组api就是套接字socket

系统主要给我们提供两组Socket api

        1.基于UDP的api ;

        2.基于TCP的api。

UDP协议和TCP协议的特点

UDP:无连接,不可靠传输,面向数据报,全双工

TCP:有连接,可靠传输,面向字节流,全双工

有/无连接:使用UDP/TCP通信的双方,各自是否需要刻意保存对端的相关信息;

可靠传输:信息发出去,尽可能的传输过去。

不可靠传输:信息发出去,不关注结果,不关注是否传输过去。

面向数据报:以一个UDP数据报为传输的基本单位。

面向字节流:以字节流为传输的基本单位,读写方式十分灵活。

全双工/半双工:一条路径双/单向通信。

二、UDP数据包套接字编程

主要提供了两个类:DatagramSocket(Socket对象),DatagramPacket(udp数据报)

关于“报”,是网络传输数据的基本单位,这些基本单位主要包括:报(datagram)(udp中使用),包(packet)(ip中使用),段(segment)(tcp中使用),帧(frame)(数据链路层中使用)。日常生活中,不会特意区分这些单位,但是写研究论文需要区分。

1.DatagramSocket API

(1)关于Socket对象

Socket对象:相当于对应到系统中的一个特殊文件(socket文件),这个文件并非对应到硬盘上的某个数据存储区域,而是对应到网卡这个硬件设备。进行网络通信,离不开socket文件这样的对象,借助socket文件对象,才能间接的操作网卡(相当于遥控器)。

向socket对象中数据,相当于通过网卡发送消息;向socket对象中数据,就相当于通过网卡接收消息。

下图片的以太网适配器就是一个有线网卡:

 无线网卡:

没有网卡就不能上网,一般是集成在主板上的。

文件广义上,代指很多计算机中的软件/硬件资源;狭义上,代指硬盘上的一块数据存储区域。

DatagramSocket是UDP Socket用于发送和接收UDP数据报。

(2)DatagramSocket方法

DatagramSocket构造方法:绑定一个端口号(服务器),也可以不显示指定客户端

方法签名

方法说明

DatagramSocket

创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端)

DatagramSocket(int port)

创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)

DatagramSocket(int port):此处Socket对象可能被客户端/服务器使用,服务器的socket往往需要关联一个具体的端口号(不变);客户端这里不需要手动指定,系统自动分配即可(可以改变)。

DatagramSocket方法:

方法签名

方法说明

void receive(DatagramPacket p)

从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)

void send(DatagramPacket p)

从此套接字发送数据报包(不会阻塞等待,直接发送)

void close()

关闭此数据报套接字,与文件操作一样,用完一定要关闭,否则会出现文件泄露问题

2. DatagramPacket API

DatagramPacket是UDP Socket发送和接收的数据报。

DatagramPacket方法

DatagramPacket构造方法:

方法签名

方法说明

DatagramPacket(byte[] buf, int length)

构造一个DatagramPacket,不需要设置地址进去,主要以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数

length

DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)

构造一个DatagramPacket,显示地址设置,通常以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length),address指定目的主机的IP和端口号

DatagramPacket方法:

方法签名

方法说明

InetAddress getAddress()

从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取 接收端主机IP地址

int getPort()

从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获 取接收端主机端口号

byte[] getData()

获取数据报中的数据

3.基于UDP Socket的客户端服务器程序(回显服务器echo server)

作用:客户端发送一个请求,服务器返回一个一模一样的响应。没有什么实际作用,只保留了最核心的发送接收环节。

服务器的三个核心工作:

  • 读取请求并解析

  • 根据请求计算响应

  • 把相应返回到客户端

整个回显服务器执行流程:

  1. 客户端读取用户输入

  2. 客户端构造请求,并发送

  3. 服务器读取用户请求数据

  4. 服务器根据请求计算响应

  5. 服务器把响应写回到客户端

  6. 客户端读取服务器的响应

  7. 客户端把响应转成字符串,并显示出来

以下代码执行顺序:

  1. 服务器先启动,执行到receive进行阻塞

  2. 客户端运行之后,从控制台读取数据,并进行send

  3. 此时客户端和服务器同时进行。客户端这边,send以后,继续往下执行,在receive里读取响应,读取以后会阻塞等待;服务器这边,就从receive返回,读取到请求数据(从客户端来的),往下走到process生成响应,然后再往下走到send并打印日志

  4. 客户端这边,当收到send回到的数据以后,就会接触阻塞,进行打印操作;服务器这边进入下一顿循环,再次阻塞到receive这里

  5. 客户端继续进行下一轮循环,阻塞在scanner.next这里等待用户执行新的内容

具体代码如下:

(1)服务器程序

  1. package network;
  2. import java.io.IOException;
  3. import java.net.DatagramPacket;
  4. import java.net.DatagramSocket;
  5. import java.net.SocketException;
  6. public class UdpEchoServer {
  7. //定义一个socket对象
  8. //通过网络通信,必须要使用socket对象
  9. private DatagramSocket socket = null;//定义一个socket对象
  10. //绑定一个端口,不一定能成功
  11. //某个端口已经被别的进程占用了,此时绑定操作就会出错
  12. //同一个主机上,一个端口,同一时刻,只能被一个进程绑定
  13. public UdpEchoServer(int port) throws SocketException {
  14. //构造socket时,指定要关联/绑定的端口
  15. socket = new DatagramSocket(port);
  16. }
  17. //启动服务器的侏罗纪
  18. public void start() throws IOException {
  19. System.out.println("服务器启动");
  20. while(true) {//时刻向客户端提供服务
  21. //每次循环,有三个步骤:
  22. //1.读取请求并解析
  23. //构造空对象
  24. DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
  25. //从网卡中读取数据
  26. //一旦服务器start,就会立即执行receive,如果此时客户端没有发送数据,此时的receive就是阻塞状态
  27. socket.receive(requestPacket);
  28. //将数据包(二进制)转换成string,处理此请求
  29. String request = new String(requestPacket.getData(),0,requestPacket.getLength());
  30. //2.根据请求计算响应(省略)
  31. String response = process(request);
  32. //3.把响应结果写回到客户端
  33. //根据response字符串。构造一个DatagramPacket
  34. //与请求packet不同,此处构造
  35. DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length
  36. //requestPacket是从客户端收来的,getSocketAddress就会得到客户端的ip和端口
  37. ,requestPacket.getSocketAddress());
  38. //客户端send请求,服务器receive请求
  39. //服务器send响应,客户端receive响应
  40. socket.send(responsePacket);
  41. System.out.printf("[%s:%D]req:%s,resp:%s\n",responsePacket.getAddress().toString(),
  42. requestPacket.getPort(),request,response);
  43. }
  44. }
  45. //此方法作用是根据请求计算响应
  46. //回显程序中没有写具体的请求和回应
  47. //如果写别的程序就可以修改process方法,根据需求重新构造响应
  48. public String process(String request) {
  49. return request;
  50. }
  51. public static void main(String[] args) throws IOException {
  52. UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
  53. udpEchoServer.start();
  54. }
  55. }

(2)客户端程序

  1. package network;
  2. import java.io.IOException;
  3. import java.net.*;
  4. import java.util.Scanner;
  5. public class UdpEchoClient {
  6. private DatagramSocket socket = null;
  7. private String serverIP;
  8. private int serverPort;
  9. public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
  10. //对于客户端来说,不需要关联端口
  11. //不代表没有端口,只是系统分配了一个吻空闲的端口
  12. socket = new DatagramSocket();
  13. this.serverIP = serverIP;
  14. this.serverPort = serverPort;
  15. }
  16. public void start() throws IOException {
  17. Scanner scanner = new Scanner(System.in);
  18. while (true) {
  19. //1.先从控制台读取一个字符串
  20. //先打印一个提示符,提示用户要输入内容
  21. System.out.println("->");
  22. String request = scanner.next();
  23. //2.把字符串构造成UDP packet,并进行发送
  24. //这个构造,就是把数据构造成DatagramPacket,一方面需要String中的getBytes数组,另一方面,需要指定服务器的ip和端口
  25. //此处不是通过inetAddress直接构造的,而是分开设置的,一方面设置字符串的ip,一方面设置端口号
  26. DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
  27. InetAddress.getByName(serverIP),serverPort);
  28. socket.send(requestPacket);
  29. //3.客户端尝试读取服务器返回的响应
  30. DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
  31. socket.receive(responsePacket);
  32. //4.把响应数据转换成String显示出来
  33. String response = new String(responsePacket.getData(),0,requestPacket.getLength());
  34. System.out.printf("%req:%s,resp:%s\n",request,response);
  35. }
  36. }
  37. public static void main(String[] args) throws IOException {
  38. UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
  39. udpEchoClient.start();
  40. }
  41. }

  对于UdpEchoServer来说,socket对象出循环就结束了生命周期,循环结束,意味着start结束,意味着main方法结束,即进程结束,当进程技结束以后,所有的文件资源就自动释放了,所以上述代码就不必显示调用close方法。 

4.单词翻译服务器

  1. package network;
  2. import java.io.IOException;
  3. import java.net.SocketException;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. //使用继承,复用前面写过的代码
  7. public class UdpDictServer extends UdpEchoServer{
  8. //使用一个hash表,存储单词
  9. //翻译的本质就是查表
  10. private Map<String,String> dict = new HashMap<>();
  11. public UdpDictServer(int port) throws SocketException {
  12. super(port);
  13. //向表中添加元素
  14. dict.put("dog","小狗");
  15. dict.put("cat","小猫");
  16. dict.put("fuck","卧槽");
  17. }
  18. //根据请求计算响应
  19. @Override
  20. public String process(String request) {
  21. return dict.getOrDefault(request,"单词没有查到");
  22. }
  23. public static void main(String[] args) throws IOException {
  24. UdpDictServer udpDictServer = new UdpDictServer(9090);
  25. udpDictServer.start();
  26. }
  27. }

三、TCP数据包套接字编程

1.SeverSocket API

ServerSocket构造方法:构造是指定一个具体端口,让服务器绑定该端口。

方法签名

方法说明

ServerSocket(int port)

创建一个服务端流套接字Socket,并绑定到指定端口

ServerSocket方法:

accept()方法就是接受,服务器是被动接受的一方,客户端是主动接受的一方

方法签名

方法说明

Socket accept()

开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待

Void close()

关闭此套接字

2.Socket API

Socket 构造方法:服务器IP和端口号,在客户端new Socket对象的时候,就会尝试和指定的ip端口的目标建立连接

方法签名

方法说明

Socket(String host, intport)

创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

Socket方法:面向字节流,通过Socket对象拿到字节流对象,就可以通过字节流对象进行数据传输了,从InputStream里面读取数据,就相当于从网卡接收;在OutPutStream里面写数据,就相当于从网卡发送。

方法签名

方法说明

InetAddress getInetAddress()

返回套接字所连接的地址

InputStream getInputStream()

返回此套接字的输入流

OutputStream getOutputStream()

返回此套接字的输出流

3.基于TCP套接字的回显程序

长连接(long connnection),指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包。

短连接(short connnection),是相对于长连接而言的概念,指的是在数据传送过程中,只在需要发送数据时才去建立一个连接,数据发送完成后则断开此连接,即每次连接只完成一项业务的发送。

基于TCP套接字的回显程序执行顺序:结合代码观看

1.TcpEchoServer线启动,运行start(),阻塞状态

2.TcpEchoClient启动,会调用socket的构造方法,和服务器进行连接,连接成功之后,accept就会返回

3.对于服务器,进入processConnection方法,尝试从客户端读取请求,由于此时客户端没有发送请求,此时读取操作处于阻塞状态;对于客户端,从控制台读取用户输入。

4.当用户输入后,客户端就会真正发送请求,同时往下执行到,读取服务器响应,再次阻塞。

5.服务器收到客户端的请求之后,从next返回,执行process,执行println将响应写回给客户端。

6.服务器重新回到循环开头位置,继续尝试读取请求,并且阻塞;客户端收到服务器的响应,就可以把结果显示出来了,同时进行下次循环,等待用户输入。

(1)TcpEchoServer

  1. package network;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.OutputStream;
  5. import java.io.PrintWriter;
  6. import java.net.ServerSocket;
  7. import java.net.Socket;
  8. import java.util.Scanner;
  9. import java.util.concurrent.ExecutorService;
  10. import java.util.concurrent.Executors;
  11. public class TcpEchoServer {
  12. //serverSocket相当于外场的拉客中介,只能有一个
  13. private ServerSocket serverSocket = null;
  14. public TcpEchoServer(int port) throws IOException {
  15. serverSocket = new ServerSocket(port);
  16. }
  17. public void start() throws IOException {
  18. //使用线程池,使得一个连接的所有请求处理完,该线程不会直接被销毁,而是换到池子里面,下次连接时还能直接使用
  19. ExecutorService executorService = Executors.newCachedThreadPool();
  20. System.out.println("服务器启动");
  21. while (true) {
  22. //clientSocket相当于内场带每个用户讲解房子的中介,可以有多个
  23. Socket clientSocket = serverSocket.accept();
  24. executorService.submit(new Runnable() {
  25. @Override
  26. public void run() {
  27. try {
  28. processConnection(clientSocket);
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. });
  34. }
  35. }
  36. //通过processConnection此方法来处理一个连接
  37. //读取请求
  38. //根据请求计算响应
  39. //将相应返回到客户端
  40. private void processConnection(Socket clientSocket) throws IOException {
  41. System.out.printf("[%s:%d]客户端上线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
  42. //try()中,允许写多个流对象,多个对象中使用分号来分割
  43. try(InputStream inputStream = clientSocket.getInputStream();
  44. OutputStream outputStream = clientSocket.getOutputStream()){
  45. //相当于字符流
  46. //使用scanner的方式读取数据
  47. Scanner scanner = new Scanner(inputStream);
  48. //将outputStream转换为字符流
  49. PrintWriter printWriter = new PrintWriter(outputStream);
  50. while(true) {
  51. //1.读取请求
  52. //每个请求是个字符串(文本数据)
  53. //请求与请求之间,使用\n来分割
  54. //hasNext():判断接下来是否还有数据
  55. //如果客户端关闭连接,hasNext就会返回为false,循环就会结束
  56. if (!scanner.hasNext()) {
  57. //读取到流结尾(也就是客户端关闭了)
  58. //输出客户端ip地址,输出客户端端口号
  59. System.out.printf("[%s:%d]客户端下线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
  60. break;
  61. }
  62. //如果客户端有数据,hasNext就会返回为true,进一步使用下面的next方法来读取出这一段字符串的内容
  63. //直接使用scanner读取一段字符串
  64. //next往后一直都,督导空白符结束(空格,换行,制表符,翻页符等等)
  65. String request = scanner.next();
  66. //2.根据请求计算响应
  67. String response = process(request);
  68. //3.把响应写回给客户端,next读操作结果是不带换行的,响应也需要带上换行
  69. printWriter.println(response);
  70. printWriter.flush();
  71. System.out.printf("[%s:%d]req:%s;resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(), request, response);
  72. //回显服务器,响应和请求一模一样
  73. }
  74. } catch(IOException e){
  75. e.printStackTrace();
  76. } finally {
  77. //clientSocket可以有多个,而且生命周期端,所以需要关闭
  78. clientSocket.close();
  79. }
  80. }
  81. //根据请求计算响应
  82. private String process(String request) {
  83. return request;
  84. }
  85. public static void main(String[] args) throws IOException {
  86. TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
  87. tcpEchoServer.start();
  88. }
  89. }

(2)TcpEchoClient

  1. package network;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.OutputStream;
  5. import java.io.PrintWriter;
  6. import java.net.Socket;
  7. import java.util.Scanner;
  8. public class TcpEchoClient {
  9. private Socket socket = null;
  10. public TcpEchoClient(String serveIp,int port) throws IOException {
  11. //将客户端于服务端建立TCP连接
  12. //连接上了,服务器的accept就会返回,在此之前,accept是处于阻塞状态的
  13. socket = new Socket(serveIp,port);
  14. }
  15. public void start() {
  16. Scanner scanner = new Scanner(System.in);
  17. try (InputStream inputStream = socket.getInputStream();
  18. OutputStream outputStream = socket.getOutputStream()) {
  19. PrintWriter printWriter = new PrintWriter(outputStream);
  20. Scanner scannerFromSocket = new Scanner(inputStream);
  21. while (true) {
  22. //1.把键盘上读取用户输入的内容
  23. System.out.print("->");
  24. String request = scanner.next();
  25. //2.把读取的内容构造成请求,发送给服务器
  26. //发送带有换行
  27. //以下步骤只是把数据写入了内存的缓冲区中,等到缓冲区满了(没有满是因为数据不够多),才会真正写网卡
  28. printWriter.println(request);
  29. //使用flush()方法,手动刷新缓冲区,将数据立即写入网卡
  30. printWriter.flush();
  31. //3.从服务器读取相应内容
  32. String response = scannerFromSocket.next();
  33. //4.把响应结果显示到控制台上
  34. System.out.printf("req:%s;resp:%s\n", request, response);
  35. }
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. }
  39. }
  40. public static void main(String[] args) throws IOException {
  41. TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
  42. tcpEchoClient.start();
  43. }
  44. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/895761
推荐阅读
相关标签
  

闽ICP备14008679号