C++实现socket通信

简介: 了解如何实现socket通信以及TCP连接的过程中发生了什么

socket 通信 实现

了解服务端与客户端如何使用socket进行通信,如何绑定socket?以及如何初始化端口/IP等信息

什么是socket ?

你可以这样理解它

Socket(套接字)是一种计算机网络通信的工具,可以让不同设备之间进行数据的传输和交流。就像电源插座一样,它允许设备们以一种友好的方式进行交流,只不过他们的语言是计算机语言。所以你可以把Socket看作是互联网世界的通信插座,让设备们能够相互聊天、发送信息,然后不断重复直到世界和平。说白了,Socket就是让计算机之间进行无线“说话”和“传文件”的火爆聚会工具。

在Linux中一切皆文件,socket可以理解成用于网络通信的文件描述符。文件的接收和发送都依靠这一个文件描述符,而通过socket这个强大的接口与配合绑定端口和ip就完成了一次通信的使命。

在Linux环境中socket的定义

/* Create a new socket of type TYPE in domain DOMAIN, using
   protocol PROTOCOL.  If PROTOCOL is zero, one is chosen automatically.
   Returns a file descriptor for the new socket, or -1 for errors.  */
#include <sys/socket.h> // 互联网协议头
extern int socket (int __domain, int __type, int __protocol) __THROW;
参数 -  
    _domain  - 需要使用的是哪种套接字?
    AF_UNIX
    UNIX 域套接字
    AF_UNSPEC
    未指定
    AF_INET
    互联网域套接字
    我们使用的比较多的是AF_INET
    - _protocol 
    使用的传输类型: 
    SOCK_DGRAM
    数据报socket
    SOCK_STREAM
    字节流socket
    SOCK_SEQPACKET
    排序数据包socket

    根据传输协议可以分为UDP和TCP,TCP一般使用SOCK_STREAM ,UDP一般使用SOCK_DGRAM
    字节流和数据报的区别:
    数据报 是网络传输的数据的基本单元,包含一个报头和数据本身,其中报头描述了数据的目的地以及和其它数据之间的关系。 同一报文的不同分组可以由不同的传输路径通过通信子网。 UDP基于 数据报 。 字节流 方式指的是仅把传输中的报文看作是一个字节序列,在 字节流 服务中,由于没有报文边界,用户进程在某一时刻可以读或写任意数量的字节。

关于字节流和数据报的区别可以参考以下链接:

TCP字节流和UDP数据报区别 - hoohack - 博客园 (cnblogs.com)

用法

  int sock_fd = socket(AF_INET, SOCK_STREAM, 0); // 使用ipv4地址
    if (sock_fd == -1)
    {
        perror("socket");
        exit(-1);
    }

详解sockaddr_in 结构体

 #include <netinet/in.h>
struct sockaddr_in {

__uint8_t sin_len; 

sa_family_t sin_family;  

in_port_t sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

sin_family指代协议族,在socket编程中只能是AF_INET

sin_port存储端口号(使用网络字节顺序)

sin_addr存储IP地址,使用in_addr这个数据结构

sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节

该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下:

struct sockaddr_in {
     short            sin_family;    // 2 字节 ,地址族,e.g. AF_INET, AF_INET6
     unsigned short   sin_port;      // 2 字节 ,16位TCP/UDP 端口号 e.g. htons(3490),
     struct in_addr   sin_addr;      // 4 字节 ,32位IP地址
     char             sin_zero[8];   // 8 字节 ,不使用
};
struct in_addr {
     unsigned long s_addr;          // 32位IPV4地址打印的时候可以调用inet_ntoa()函数将其转换为char *类型.
};

sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。

总结

二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。

用法

struct sockaddr_in addr;
    addr.sin_addr.s_addr = inet_addr("127.0.0,1"); // 使用本地地址
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1234); // 端口
    memset(&addr, 0, sizeof(addr));

socket 通信系列函数

常用的socket通信函数

connect 函数

函数作用:

对客户端进行连接

返回值:

成功返回 0,失败返回 -1

extern int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);
  - 参数
      _fd  需要传递的文件描述符
      __CONST_SOCKADDR_ARG __addr 
       // 需要传递强转后的 struct sockaddr_in 结构体,里面包含了ip,端口号 ,地址族等信息
      socklen_t __len 
      // -  struct sockaddr_in 结构体的大小

用法

connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)); // 连接

listen 函数

作用:

将套接字文件描述符从主动转为被动文件描述符,然后用于被动监听客户端的连接

返回值:

成功返回 0,失败返回 -1

函数定义

/* Prepare to accept connections on socket FD.
   N connection requests will be queued before further requests are refused.
   Returns 0 on success, -1 for errors.  */
extern int listen (int __fd, int __n) __THROW;
  参数 -
      文件描述符
      一个队列
      这个队列用于记录正在连接但是还没有连接完成的客户端,一般设置队列的容量为2,3即可。队列的最大容量需要小于30

用法

listen(sock_fd, 20);

accept 函数

作用:

被动监听客户端发起的tcp连接请求,三次握手后连接建立成功。客户端connect函数请求发起连接。
连接成功后服务器的tcp协议会记录客端的ip和端口,如果是跨网通信,记录ip的就是客户端所在路由器的公网ip

返回值:

成功返回一个可以通信的文件描述符专门用于可以通信成功的客户端,失败返回 -1并设置error

函数定义

/* Await a connection on socket FD.
   When a connection arrives, open a new socket to communicate with it,
   set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
   peer and *ADDR_LEN to the address's actual length, and return the
   new socket's descriptor, or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int accept (int __fd, __SOCKADDR_ARG __addr,
           socklen_t *__restrict __addr_len);

用法

 struct sockaddr client_in; // 接受客户端的请求
    socklen_t x = sizeof(client_in);
    int clnt_fd = accept(sock_fd, (struct sockaddr *)&client_in, &x);

bind 函数

作用:把用于通信的地址和端口号绑定到socket上

绑定的一定是自己的 ip和和端口,不是对方的;比如对于TCP服务器来说绑定的就是服务器自己的ip和端口

返回值:

成功返回 0,失败返回 -1

函数定义

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

/* Give the socket FD the local address ADDR (which is LEN bytes long).  */
extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)
    - 参数
     _fd  传递的文件描述符
     __CONST_SOCKADDR_ARG __addr  
    强转后的struct sockaddr_in 结构体,包含端口号,ip地址等信息

用法

    int ret = bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)); // 绑定socket
    if (ret == -1)
    {
        perror("bind");
        exit(-1); //
    }

案例

服务端与客户端进行通信

通信案例 : 服务端接收到客户端的消息打印出来,然后再次把数据发送回去给客户端

服务端 server

#include <arpa/inet.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <iostream>
using std::cin;
using std::cout;
int main() {
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in addr;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1234);

    int ret = bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret == -1) {
        perror("bind");
        exit(-1);
    }

    listen(sock_fd, 1); // 进入监听状态

    struct sockaddr client_in;
    socklen_t x = sizeof(client_in);
    int clnt_fd = accept(sock_fd, (struct sockaddr *)&client_in, &x);
    if (clnt_fd == -1) {
        perror("accept");
        exit(-1);
    }

    char buffer[1024];
    ret = read(clnt_fd, buffer, sizeof(buffer)); // 从客户端接收字符串
    if (ret > 0) {
        cout << "Received message from client: " << buffer << std::endl;
    }

    char s[] = "hello i am server";
    write(clnt_fd, s, strlen(s) + 1); // 向客户端发送数据

    close(clnt_fd);
    close(sock_fd);
    return 0;
}

客户端

#include <arpa/inet.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <iostream>
using std::cin;
using std::cout;
int main(){      
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in serv_addr;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(1234);

    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    char s[] = "hello i am client ";
    write(sock, s, strlen(s) + 1); // 把字符串发送给服务器

    char buffer[1024];
    read(sock, buffer, sizeof(buffer)); // 从服务器接收字符串
    cout << "Received message from server: " << buffer << std::endl;

    close(sock);
    return 0;
}

多进程版本TCP服务端实现

经过前面的案例和相关函数的介绍,我们也大概明白了网络编程是怎么回事;现在让我们来动手实现一个多进程通信的TCP客户端吧!

步骤分为以下八个流程

  1. 创建socket
  2. 初始化 sockaddr_in结构体
  3. 使用bind函数将socket绑定到sockaddr_in结构体上
  4. listen监听socket
  5. 定义用于与客户端通信的socket,通过accept函数返回。但是在此之前,需要先将客户端的sockaddr_in结构体绑定到accept
  6. 使用fork函数创建子进程,父进程继续监听客户端请求
  7. 从客户端读取数据
  8. 发送数据到服务端

创建socket

创建服务端的socket,并检查返回值

 int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1)
    {
   
        perror("socket");
        exit(-1);
    }

初始化端口,ip地址

初始化sockaddr_in结构体中的端口,ip地址等信息

   struct sockaddr_in addr;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器地址
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1234);

绑定socket

绑定socket到sockaddr_in 结构体(端口号,ip地址)上

    int ret = bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret == -1)
    {
   
        perror("bind");
        exit(-1);
    }

监听socket

监听socket中的端口中是否有客户端进来

 listen(sock_fd, 1); // 进入监听状态
    int clnt_fd;

accept等待三次握手成功后的文件描述符

其中clnt_fd是服务端成功与客户端建立起连接后的文件描述符

 struct sockaddr client_in; // 客户端地址
        socklen_t x = sizeof(client_in);
        clnt_fd = accept(sock_fd, (struct sockaddr *)&client_in, &x); // clnt_fd是客户端的文件描述符

创建子进程

子进程处理请求,父进程继续监听

 pid_t pid = fork(); // 多进程为客户端提供服务
        if (pid == -1)
        {
   
            perror("fork");
            exit(-1);
        }
        if (pid > 0)
        {
   
            continue; // 父进程继续监听
        }

交互

服务端与客户端进行通信

 char buffer[1024];
        while (1)
        {
   
            ret = read(clnt_fd, buffer, sizeof(buffer)); // 从客户端接收字符串
            if (ret > 0)
            {
   
                cout << "Received message from client: " << buffer << std::endl;
            }

            char s[] = "hello i am server";
            write(clnt_fd, s, strlen(s) + 1); // 向客户端发送数据
        }

四次挥手断开连接

   close(clnt_fd);
    close(sock_fd);

完整代码

#include <arpa/inet.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <iostream>
using std::cin;
using std::cout;
int main()
{
   
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1)
    {
   
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in addr;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器地址
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1234);

    int ret = bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret == -1)
    {
   
        perror("bind");
        exit(-1);
    }

    listen(sock_fd, 1); // 进入监听状态
    int clnt_fd;
    while (1)
    {
   

        struct sockaddr client_in; // 客户端地址
        socklen_t x = sizeof(client_in);
        clnt_fd = accept(sock_fd, (struct sockaddr *)&client_in, &x); // clnt_fd是客户端的文件描述符
        if (clnt_fd == -1)
        {
   
            perror("accept");
            exit(-1);
        }

        pid_t pid = fork(); // 多进程为客户端提供服务
        if (pid == -1)
        {
   
            perror("fork");
            exit(-1);
        }
        if (pid > 0)
        {
   
            continue; // 父进程继续监听
        }

        char buffer[1024];
        while (1)
        {
   
            ret = read(clnt_fd, buffer, sizeof(buffer)); // 从客户端接收字符串
            if (ret > 0)
            {
   
                cout << "Received message from client: " << buffer << std::endl;
            }

            char s[] = "hello i am server";
            write(clnt_fd, s, strlen(s) + 1); // 向客户端发送数据
        }
    }

    close(clnt_fd);
    close(sock_fd);
    return 0;
}

客户端实现

客户端的实现方式就简单多了,没有服务端那么多流程!让我们看看它有哪几部?首先还是要先创建socket文件描述符,再初始化IP地址,端口等信息,最后发起连接。此时呢,服务端也就可以跟服务端进行通信了

#include <arpa/inet.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <iostream>
using std::cin;
using std::cout;
int main(){
         
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
   
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in serv_addr;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(1234);

    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    char s[] = "hello i am client ";
    write(sock, s, strlen(s) + 1); // 把字符串发送给服务器

    char buffer[1024];
    read(sock, buffer, sizeof(buffer)); // 从服务器接收字符串
    cout << "Received message from server: " << buffer << std::endl;

    close(sock);
    return 0;
}

面向对象优化

到现在可还是觉得写在主函数里面太过于繁琐,能不能简化一点?让代码的可读性更好!下面我们将代码都封装进了一个类中,并且支持手动输入来进行双向通信!

服务端

#include <cstdio>
#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include<pthread.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
using namespace std;
class TCP_Server{
   
    private:
        int sock_fd;  // 服务端的socket文件描述符
        int conn_fd; // accept() 返回的与客户端连接的socket文件描述符
        uint16_t port; // 服务端的端口号
        std::string ip;  // 服务端的ip地址
        struct sockaddr_in server_addr;
        public:
        TCP_Server():sock_fd(-1),conn_fd(-1){
   

        }

        bool init_TcP_Server(uint16_t _port ,std::string _ip){
   
               sock_fd  = socket(AF_INET,SOCK_STREAM,0);{
   
                  perror("socket");
                  cerr<<"socket error"<<endl;  // 发送标准错误信息
               }
               memset(&server_addr,0,sizeof(server_addr));
               server_addr.sin_family = AF_INET;
               server_addr.sin_port = htons(_port);
               server_addr.sin_addr.s_addr  = inet_addr(_ip.c_str());

               if(bind(sock_fd,(struct sockaddr*)&server_addr,sizeof(server_addr))==-1){
   
                     perror("bind");
                        cerr<<"bind error"<<endl;
                        return false;
               }
               listen(sock_fd,10);
               cout<<"server init success"<<endl;  // 初始化成功
                return true; 
        }

        void accept_client(){
   
             while(1){
   
                  struct sockaddr_in client_addr;
                  socklen_t len = sizeof(client_addr);
                  conn_fd = accept(sock_fd,(struct sockaddr*)&client_addr,&len);// 用于通信的socket文件描述符
                  if(conn_fd==-1){
   
                      perror("accept");
                      cerr<<"accept error"<<endl;
                  }

                    cout<<"accept a new client"<<endl;
                    pid_t pid=  fork();
                    if(pid==-1)
                    {
   
                         perror("fork");
                         exit(-1);
                    }
                    if(pid>0)
                    {
   
                        continue; // 父进程继续监听
                    }

                      char buffer[1024]; // 用于存储客户端发送的数据
                      while(1){
   
                           // 从键盘读取数据
                            int ret = read(conn_fd,buffer,sizeof(buffer));
                            if(ret>0)
                            {
   
                                cout<<"client send data: "<<buffer<<endl;
                            }
                            char s[1024];
                            // 开始从键盘输入
                            cin>>s;
                            write(conn_fd,s,(strlen(s)+1)); // 发送数据
                      }
             }
        }

        void close(){
   
            ::close(sock_fd);  // 关闭服务端的socket文件描述符
            ::close(conn_fd); // 关闭与客户端连接的socket文件描述符
        }

}; 
int main()
{
   

     TCP_Server server;
     server.init_TcP_Server(1234,"127.0.0.1");  // 初始化服务端
     server.accept_client();  // 接受客户端的连接
        server.close();  // 关闭服务端
     return 0;
}

客户端

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <iostream>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MAXLINE 1024
using namespace std;
class TCP_Client{
   
    private:
    int sock; // socket descriptor
   struct sockaddr_in server; // server's address information
   char buffer[MAXLINE]; // buffer for data
   uint16_t port; // server's port
   string ip; // server's ip
   public:
   TCP_Client():sock(-1){
   }
   ~TCP_Client(){
   
       close(sock);
   }
    bool init(string ip,uint16_t port)
    {
   
         sock = socket(AF_INET,SOCK_STREAM,0);
         if(sock==-1){
   
             perror("socket");
                return false;
         }
           memset(&server,0,sizeof(server));
            server.sin_addr.s_addr = inet_addr(ip.c_str());
            server.sin_family = AF_INET;
            server.sin_port = htons(port);

           connect(sock,(struct sockaddr*)&server,sizeof(server));
           cout<<"Connected to server"<<endl;
           return true;
    }

    void write_data(){
   
         char s[MAXLINE];
         cin.getline(s,MAXLINE); // get input from user
         write(sock,s,strlen(s)+1); // send data to server

          char buffer[MAXLINE];
          read(sock,buffer,MAXLINE); // read data from server
            cout<<"Server: "<<buffer<<endl; // print data from server        
    }



}; 
int main()
{
   

       TCP_Client client;
       client.init("127.0.0.1",1234);
       client.write_data();
         return 0;
}
目录
相关文章
|
2月前
|
NoSQL 网络协议 Linux
Redis的实现一:c、c++的网络通信编程技术,先实现server和client的通信
本文介绍了使用C/C++进行网络通信编程的基础知识,包括创建socket、设置套接字选项、绑定地址、监听连接以及循环接受和处理客户端请求的基本步骤。
60 6
|
6月前
|
缓存 监控 Java
Java Socket编程最佳实践:优化客户端-服务器通信性能
【6月更文挑战第21天】Java Socket编程优化涉及识别性能瓶颈,如网络延迟和CPU计算。使用非阻塞I/O(NIO)和多路复用技术提升并发处理能力,减少线程上下文切换。缓存利用可减少I/O操作,异步I/O(AIO)进一步提高效率。持续监控系统性能是关键。通过实践这些策略,开发者能构建高效稳定的通信系统。
193 1
|
4月前
|
Python
python socket 简单通信
python socket 简单通信
50 1
|
4月前
|
网络协议 安全 网络安全
网络编程:基于socket的TCP/IP通信。
网络编程:基于socket的TCP/IP通信。
291 0
|
6月前
|
IDE Java 开发工具
从零开始学Java Socket编程:客户端与服务器通信实战
【6月更文挑战第21天】Java Socket编程教程带你从零开始构建简单的客户端-服务器通信。安装JDK后,在命令行分别运行`SimpleServer`和`SimpleClient`。服务器监听端口,接收并回显客户端消息;客户端连接服务器,发送“Hello, Server!”并显示服务器响应。这是网络通信基础,为更复杂的网络应用打下基础。开始你的Socket编程之旅吧!
105 3
|
6月前
|
Java 数据挖掘 开发者
Java网络编程进阶:Socket通信的高级特性与应用
【6月更文挑战第21天】Java Socket通信是分布式应用的基础,涉及高级特性如多路复用(Selector)和零拷贝,提升效率与响应速度。结合NIO和AIO,适用于高并发场景如游戏服务器和实时数据分析。示例展示了基于NIO的多路复用服务器实现。随着技术发展,WebSockets、HTTP/2、QUIC等新协议正变革网络通信,掌握Socket高级特性为应对未来挑战准备。
59 1
|
2月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【10月更文挑战第10天】网络协议定义了机器间通信的标准格式,确保信息准确无损地传输。主要分为两种模型:OSI七层模型与TCP/IP模型。
|
6月前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
115 0
|
6月前
|
安全 Java 网络安全
Java Socket编程教程:构建安全可靠的客户端-服务器通信
【6月更文挑战第21天】构建安全的Java Socket通信涉及SSL/TLS加密、异常处理和重连策略。示例中,`SecureServer`使用SSLServerSocketFactory创建加密连接,而`ReliableClient`展示异常捕获与自动重连。理解安全意识,如防数据截获和中间人攻击,是首要步骤。通过良好的编程实践,确保网络应用在复杂环境中稳定且安全。
115 0
|
2月前
|
网络协议 Linux 网络性能优化
Linux C/C++之TCP / UDP通信
这篇文章详细介绍了Linux下C/C++语言实现TCP和UDP通信的方法,包括网络基础、通信模型、编程示例以及TCP和UDP的优缺点比较。
52 0
Linux C/C++之TCP / UDP通信