Linux网络编程

2018-11-23  本文已影响0人  MagicalGuy

TCP

service.c
服务端

#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
int main(void)
{
    int sfp,nfp;
    struct sockaddr_in s_add,c_add;
    int sin_size;
    unsigned short portnum=0x8888;
    char buff[100]={0};

    printf("Hello,welcome to my server !\r\n");
    sfp = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sfp)
    {
        printf("socket fail ! \r\n");
        return -1;
    }
    printf("socket ok !\r\n");

    bzero(&s_add,sizeof(struct sockaddr_in));
    s_add.sin_family=AF_INET;
    s_add.sin_addr.s_addr=htonl(INADDR_ANY);
    s_add.sin_port=htons(portnum);

    if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
    {
        printf("bind fail !\r\n");
        return -1;
    }
    printf("bind ok !\r\n");

    if(-1 == listen(sfp,5))
    {
        printf("listen fail !\r\n");
        return -1;
    }
    printf("listen ok\r\n");
    while(1)
    {
        sin_size = sizeof(struct sockaddr_in);

        nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);
        if(-1 == nfp)
        {
            printf("accept fail !\r\n");
            return -1;
        }
        read(nfp,buff,100);
        printf("received:%s\n", buff);

        if(-1 == write(nfp,buff,strlen(buff)+1))
        {
            printf("write fail!\r\n");
            return -1;
        }
        printf("write ok!\r\n");
        close(nfp);
    }
    close(sfp);
    return 0;
}

makefile:

server:server.o
    gcc -o server server.o
server.o:server.c
    gcc -c server.c -o server.o
clean:
    rm -f *.o server

=====================
客户端
client.c

#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(void)
{
    int cfd;
    int recbytes;
    int sin_size;
    char buffer[1024]={0};   
    struct sockaddr_in s_add,c_add;
    unsigned short portnum=0x8888; 
    printf("Hello,welcome to client !\r\n");

    cfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == cfd)
    {
        printf("socket fail ! \r\n");
        return -1;
    }
    printf("socket ok !\r\n");

    bzero(&s_add,sizeof(struct sockaddr_in));
    s_add.sin_family=AF_INET;
    s_add.sin_addr.s_addr= inet_addr("127.0.0.1");
    s_add.sin_port=htons(portnum);
    printf("s_addr = %#x ,port : %#x\r\n",s_add.sin_addr.s_addr,s_add.sin_port);

    if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
    {
        printf("connect fail !\r\n");
        return -1;
    }
    printf("connect ok !\r\n");
    write(cfd,"hello tcp socket", strlen("hello tcp socket")+1);

    if(-1 == (recbytes = read(cfd,buffer,1024)))
    {
        printf("read data fail !\r\n");
        return -1;
    }
    printf("read ok\r\nREC:\r\n");
    buffer[recbytes]='\0';
    printf("%s\r\n",buffer);
    getchar();
    close(cfd);
    return 0;
}

makefile:

client:client.o
    gcc -o client client.o
client.o:client.c
    gcc -c client.c -o client.o
clean:
    rm -f *.o client

UDP

a端
a.c

#include <stdio.h>
#include <stdlib.h>

#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>

int port=6789;
int main(int argc, char** argv) 
{
    int fd; //套接口描述字
    int i=0;
    char buf[80];
    struct sockaddr_in address;//处理网络通信的地址

    bzero(&address,sizeof(address));
    address.sin_family=AF_INET;
    address.sin_addr.s_addr=inet_addr("127.0.0.1");//这里不一样
    address.sin_port=htons(port);

    //创建一个 UDP socket

    fd=socket(AF_INET,SOCK_DGRAM,0);//IPV4 SOCK_DGRAM 数据报套接字(UDP协议)

    for(i=0;i<20;i++)
    {
        /*
        * sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567" 
        * 将格式化后到 字符串存放到s当中
        */
        sprintf(buf,"hello udp socket %d",i);

        /*int PASCAL FAR sendto( SOCKET s, const char FAR* buf, int len, int flags,const struct sockaddr FAR* to, int tolen);  
        * s:一个标识套接口的描述字。 
        * buf:包含待发送数据的缓冲区。  
        * len:buf缓冲区中数据的长度。 
        * flags:调用方式标志位。  
        * to:(可选)指针,指向目的套接口的地址。 
        * tolen:to所指地址的长度。 
           */
        sendto(fd,buf,sizeof(buf),0,(struct sockaddr *)&address,sizeof(address));
        sleep(1);
    }

    sprintf(buf,"stop");
    sendto(fd,buf,sizeof(buf),0,(struct sockaddr *)&address,sizeof(address));//发送stop 命令
    close(fd);
    printf("Messages Sent,terminating\n");

    exit(0);

    return (EXIT_SUCCESS);
}

makefile:

client:a.o
    gcc -o client a.o
a.o:a.c
    gcc -c a.c -o a.o
clean:
    rm -f *.o client

=========================
b端
b.c

#include <stdio.h>
#include <stdlib.h>

#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

int port=6789;

int main(int argc, char** argv) 
{

    int sin_len;
    char message[256];

    int fd;
    struct sockaddr_in sin;
    printf("Waiting for data from sender \n");

    bzero(&sin,sizeof(sin));
    sin.sin_family=AF_INET;
    sin.sin_addr.s_addr=htonl(INADDR_ANY);
    sin.sin_port=htons(port);
    sin_len=sizeof(sin);

    fd=socket(AF_INET,SOCK_DGRAM,0);
    bind(fd,(struct sockaddr *)&sin,sizeof(sin));

    while(1)
    {
        recvfrom(fd,message,sizeof(message),0,(struct sockaddr *)&sin,&sin_len);
        printf("Response from server:%s\n",message);
        if(strncmp(message,"stop",4) == 0)//接受到的消息为 “stop”
        {

            printf("Sender has told me to end the connection\n");
            break;
        }
    }

    close(fd);
    exit(0);

    return (EXIT_SUCCESS);
}

makefile:

server:b.o
    gcc -o server b.o
b.o:b.c
    gcc -c b.c -o b.o
clean:
    rm -f *.o server

服务端select模型

服务器端
main.cpp

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h> 
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

using namespace std;
bool g_bwillexit = false;
typedef struct _CmdParam
{
    char type;
    char quename[128];
    char conntype;
    
}CmdParam, *PCmdParam;

typedef struct _CmdReply
{
    char reply[20];
}CmdReply;

#define MYPORT 1234    // the port users will be connecting to
#define BACKLOG 1500    // how many pending connections queue will hold
#define BUF_SIZE 1024 

int fd_A[BACKLOG];    // 客户端连接套接字数组accepted connection fd
int conn_amount;    // current connection amount

int  writen(int s, char *buff, int len)
{ 
        
    int nLeft,idx, ret;   
    nLeft = len;   
    idx = 0;
    ret = 0;

    while(nLeft>0)
    {   
                if ((ret=send(s, &buff[idx], nLeft,0))== -1)
                {
                        break;
                } 
                nLeft -= ret;   
                idx += ret;
    }
    return idx;
}

int  readn(int s,char *buf,int len)
{
 
         int nRev = 0, recvCount = 0;
         int length = len;

         if(buf == NULL)
          return 0;

        while(length>0)
         {
                  nRev = recv(s,buf+recvCount,length, 0);
                  if(nRev == -1  || nRev == 0)
                  {
                          break;
                  }
                  length -= nRev;
                  recvCount += nRev;
         }
 
         return recvCount;
}

static void  GetMyProcPath(char *buff)
{
   char exec_name [1024] = {0};
   readlink ("/proc/self/exe", exec_name, 1024); 
   char *pSlash = strrchr(exec_name, '/');
   *pSlash = '\0';
   strcpy(buff, exec_name);
   return;
}


int main(int argc, char *argv[])
{
    int sock_fd, new_fd;  // listen on sock_fd, new connection on new_fd
    struct sockaddr_in server_addr;    // server address information
    struct sockaddr_in client_addr; // connector's address information
    socklen_t sin_size;
    int yes = 1;
    char buf[BUF_SIZE];
    int ret;
    int i;


    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        printf("socket\n");
        exit(1);
    }

    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
    {
        printf("setsockopt\n");
        exit(1);
    }
    
    server_addr.sin_family = AF_INET;         // host byte order
    server_addr.sin_port = htons(MYPORT);     // short, network byte order
    server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
    memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));

    if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) 
    {
        printf("bind error\n");
        exit(1);
    }

    if (listen(sock_fd, BACKLOG) == -1)
    {
        printf("listen\n");
        exit(1);
    }

    printf("listen port %d\n", MYPORT);

    fd_set fdsr;
    int maxsock;
    struct timeval tv;

    conn_amount = 0;
    sin_size = sizeof(client_addr);
    maxsock = sock_fd;
    while (g_bwillexit == false)
    {
        // initialize file descriptor set
        FD_ZERO(&fdsr);
        FD_SET(sock_fd, &fdsr);

        // timeout setting
        tv.tv_sec = 30;
        tv.tv_usec = 0;

        // add active connection to fd set
        for (i = 0; i < BACKLOG; i++)
        {
            if (fd_A[i] != 0)
            {
                FD_SET(fd_A[i], &fdsr);//fd_set fdsr;里面记录了每个客户端的套接字
            }
        }

        ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);//开始通过fdsr对每个套接字进行监听
        if (ret < 0)
        {
            printf("select error\n");
            break;
        }
         else if (ret == 0)
        {
            printf("timeout\n");
            continue;
        }

        // check every fd in the set
        // 处理已有的连接通信的,收发数据和断开连接
        for (i = 0; i < conn_amount; i++)
        {
            if (FD_ISSET(fd_A[i], &fdsr))//某个客户端套接字有响应
            {
                CmdParam cmd ={0};
                ret = readn(fd_A[i], (char *)&cmd, sizeof(cmd));
                if (ret <= 0)
                {   // client close
                    printf("client[%d] close\n", i);
                    close(fd_A[i]);
                    FD_CLR(fd_A[i], &fdsr);
                    fd_A[i] = fd_A[conn_amount-1];
                    fd_A[conn_amount-1]=0;
                    conn_amount--;
                }
                else
                {   // receive data
                    printf("read:%d byetes\n", ret);
                    if (cmd.type == 'c' && (cmd.conntype == 's' || cmd.conntype=='l'))
                    {
                        printf("c and (s or l) command received\n");
                        CmdReply reply = {0};
                        strcpy(reply.reply, "ok!");
                        writen(fd_A[i], (char *)&reply, sizeof(reply));
                    }
                    else  if (cmd.type == 'c')
                    {
                        printf("c command received\n");
                        CmdReply reply = {0};
                        strcpy(reply.reply, "ok!");
                        writen(fd_A[i], (char *)&reply, sizeof(reply));
                    }
                    else if (cmd.type == 'q')
                    {
                        printf("q command received\n");     
                    }
                    else
                    {
                        printf("unknown command received\n");
                        CmdReply reply = {0};
                        strcpy(reply.reply, "unknown cmd");
                        writen(fd_A[i], (char *)&reply, sizeof(reply));
                    }
              }
          }
     }

     // check whether a new connection comes
     // 接受处理新的客户端连接
     if (FD_ISSET(sock_fd, &fdsr))//sock_fd belongs to server
     {
            new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
            if (new_fd <= 0)
            {
                printf("accept error\n");
                continue;
            }

            // add to fd queue
            if (conn_amount < BACKLOG)
            {
                fd_A[conn_amount++] = new_fd;
                printf("new connection client[%d] %s:%d\n", conn_amount,
                        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                if (new_fd > maxsock)
                    maxsock = new_fd;
            }
            else
            {
                printf("max connections arrive, exit\n");
                CmdReply reply = {0};
                strcpy(reply.reply, "reject");
                writen(new_fd, (char *)&reply, sizeof(reply));
                close(new_fd);
            }
        }
   }
  // close other connections
  for (i = 0; i < BACKLOG; i++)
  {
        if (fd_A[i] != 0)
        {
            close(fd_A[i]);
        }
  }
  return 0;
  
}

makefile:

overpasspull_server:main.o
    g++ -g -o  overpasspull_server main.o
main.o:main.cpp
    g++ -g -c main.cpp -o main.o
clean:
    rm -f *.o overpass

=======================
客户端
main.cpp

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

#define MYPORT 1234    // the port users will be connecting to

#define BACKLOG 5     // how many pending connections queue will hold
#define BUF_SIZE 200

int  writen(int s, char *buff, int len)
{ 
        
    int nLeft,idx, ret;   
    nLeft = len;   
    idx = 0;
    ret = 0;

    while(nLeft>0)
    {   
                if ((ret=send(s, &buff[idx], nLeft,0))== -1)
                {
                        break;
                } 
                nLeft -= ret;   
                idx += ret;
    }
    return idx;
}

int  readn(int s,char *buf,int len)
{
 
         int nRev = 0, recvCount = 0;
         int length = len;

         if(buf == NULL)
          return 0;

        while(length>0)
         {
                  nRev = recv(s,buf+recvCount,length, 0);
                  if(nRev == -1  || nRev == 0)
                  {
                          break;
                  }
                  length -= nRev;
                  recvCount += nRev;
         }
 
         return recvCount;
}

#define MYPORT 1234
typedef struct _CmdParam
{
        char type;
        char quename[128];
        char conntype;

}CmdParam, *PCmdParam;
typedef struct _CmdReply
{
        char reply[20];
}CmdReply; 

int main(int argc, char *argv[])
{

    int sockfd,sock_dt; 
    printf("#####################################################\n"); 
    printf("socket test      by pafone   19th,April,2009\n"); 
    printf("#####################################################\n"); 
    struct sockaddr_in my_addr;//local ip info 
    struct sockaddr_in dest_addr; //destnation ip info 

    //int destport = atoi(argv[2]); 
    if(-1 == (sockfd = socket(AF_INET,SOCK_STREAM,0)) ) 
    { 
        printf("error in create socket\n"); 
        exit(0); 
    } 
    dest_addr.sin_family = AF_INET; 
    dest_addr.sin_port = htons(MYPORT); 
    dest_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
    //bzero(&dest_addr.sin_zero,0,8); 
    memset(&dest_addr.sin_zero,0,8); 
    //connect 
    if(-1 == connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(struct sockaddr))) 
    { 
        printf("connect error\n"); 
        exit(0); 
    } 
    int n_send_len; 
    CmdParam cmd ={0};
    cmd.type = 'c';
    //strcpy(cmd.queueip, "10.200.193.169");
    strcpy(cmd.quename, "message_stream");
    //cmd.conntype = 's';
    cmd.conntype = 's';
    //strcpy(cmd.queueport, "3371");
    n_send_len = writen(sockfd, (char *)&cmd, sizeof(cmd));
    printf("%d bytes sent\n",n_send_len); 
    sleep(5);

    CmdReply reply = {0};

    int n_read_len = readn(sockfd, (char *)&reply, sizeof(reply));
    if (n_read_len)
    {
        printf("reply:%s, length:%d\n", reply.reply, n_read_len);
    }
    sleep(5);
    memset(&cmd, 0, sizeof(cmd));
    cmd.type = 'q';
    writen(sockfd, (char *)&cmd, sizeof(cmd));
    
    close(sockfd); 
    
    return 0;
}

makefile:

overpasspull_client:main.o
    g++ -g -o  overpasspull_client main.o
main.o:main.cpp
    g++ -g -c main.cpp -o main.o
clean:
    rm -f *.o overpass

服务端epoll模型

服务端
server.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

#define BUFFER_SIZE 40
#define MAX_EVENTS 10

int main(int argc, char * argv[])
{
    int server_sockfd;//服务器端套接字
    int client_sockfd;//客户端套接字
    int len;
    struct sockaddr_in my_addr;  //服务器网络地址结构体
    struct sockaddr_in remote_addr; //客户端网络地址结构体
    int sin_size;
    char buf[BUFFER_SIZE];  //数据传送的缓冲区
    memset (&my_addr ,0,sizeof(my_addr)); //数据初始化--清零
    my_addr.sin_family=AF_INET; //设置为IP通信
    my_addr.sin_addr.s_addr=INADDR_ANY;// 服务器IP地址--允许连接到所有本地地址上
    my_addr.sin_port=htons(8000); //服务器端口号
    //创建服务器端套接字--IPv4协议,面向连接通信,TCP协议
    if((server sockfd=socket(PF_INET, SOCK_STREAM,0))<0)
    {
        perror("socket");return 1 ;
    }
    //将套接字绑定到服务器的网络地址上
    if (bind(server_sockfd, (struct sockaddr *)&my_addr ,sizeof(struct sockaddr))<0)
    {
        perror("bind");
        return 1;
    }
    //监听连接请求--监听队列长度为5
    listen(server_sockfd,5);
    sin_size=sizeof(struct sockaddr_in) ;
    //创建一个epoll句柄
    int epoll_fd;
    epoll_fd=epoll_create (MAX_EVENTS);
    if(epoll_fd==-1)
    {
        perror("epoll_create failed");
        exit(EXIT_FAILURE) ;
    }
    struct epoll_event ev;//epoll事件结构体
    struct epoll_event events [MAX_EVENTS];//事件监听队列
    ev.events=EPOLLIN;
    ev.data.fd=server_sockfd;
    //向epoll注册server_sockfd监听事件
    if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD,server_sockfd,&ev)==-1)
    {

      perror("epll_ctl:server_sockfd register failed");
      exit(EXIT_FAILURE);
    }
    int nfds;//epoll监听事件发生的个数
    while(1)
    {
        //等待事件发生
        nfds=epoll_wait(epoll_fd,events ,MAX_EVENTS,-1);
        if(nfds==-1)
        {
            printf("start epoll_wait failed\n");
            break;
        }
        int i;
        for(i=0;i<nfds;i++)
        {
            //客户端有新的连接请求
            if(events[i].data.fd==server_sockfd)
            {
                //等待客户端连接请求到达
                if((client_sockfd=accept(server_sockfd, (struct sockaddr * )&remote_addr ,&sin_size))<0)
                {
                    printf("accept client_sockfd failed\n");
                    exit(EXIT_FAILURE);
                }
                //向epoll注册client_sockfd监听事件
                ev.events=EPOLLIN;
                ev.data.fd=client_sockfd;
                if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD,client_sockfd,&ev)==-1)
                {
                    printf("epoll_ctl:client_sockfd register failed\n");
                    exit(EXIT_FAILURE);
                    printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));
                }
                //客户端有数据发送过来
                else
                {
                    client_sockfd=events[i].data.fd;
                    len=recv(client_sockfd , buf , BUFFER_SIZE,0);
                    if(len<=0)
                    {
                        ev.events=EPOLLIN;
                        ev.data.fd=client_sockfd;
                        epoll_ctl(epoll_fd,EPOLL_CTL_DEL,client_sockfd ,&ev);
                        close(client_sockfd);
                        printf("client exit %d\n" ,client_sockfd);
                        break;
                    }
                    printf("receive from client:%s\n" ,buf) ;
                    send(client_spckfd,buf, BUFFER_SIZE,0);
                }
            }
        }
        return 0;
    }

=============================
客户端
client.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>

#define BUFFER_SIZE 40

int main(int argc, char *argv[]){
    int client_sockfd;int len;
    struct sockaddr_in remote_addr; //服务器端网络地址结构体
    char buf[BUFFER_SIZE]; // 数据传送的缓冲区
    memset(&remote_addr ,θ ,sizeof(remote_addr)); //数据初始化--清零
    remote_addr.sin_family=AF_INET; //设置为IP通信
    remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");// 服务器IP地址
    remote_addr.sin_port=htons(8000); //服务器端口号
    //创建客户端套接字--IPv4协议,面向连接通信,TCP协议
    if((client_sockfd=socket(PF_INET , SOCK_STREAM,0))<0)
        {
            perror("client socket creation failed");
            exit(EXIT_FAILURE);
        }
    //将套接字绑定到服务器的网络地址上
    if(connect(client_sockfd, (struct sockaddr *)&remote_addr , sizeof(struct sockaddr))<0)
    {
        perror ("connect to server failed");
        exit(EXIT_FAILURE);
    }

    //循环监听服务器请求
    while(1)
        {
        printf("Please input the message:");
        scanf("%s" ,buf);
        //exit
        if(strcmp(buf,"exit")==0)
            {
            break;
            }
            send(client_sockfd, buf , BUFFER_SIZE,0);
            //接收服务端信息
            len=recv(client_sockfd, buf , BUFFER_SIZE,0);
            printf("receive from server :%s\n",buf);
            if(len<0)
            {
                perror("receive from server failed");
                exit(EXIT_FAILURE);
            }
        }
        close(client_sockfd);//关闭套接字
        return 0;
}

makefile:

.PHONY:all
all:server client
server:
    gcc server.c -o server
    gcc client.c -o client
clean:
    rm -f server client

Select与POLL区别

Select与POLL,EPOLL都是C种同步的10多路复用模型实现机制,它们的区别为:
1.select的句柄数目受限,在linux/posix_types.h头 文件有这样的声明: #define __FD_SETSIZE 1024表示select最多同时监听1024个fd(64位机默认是2048)。而epoll没有,它的限制是最大的打开文件句柄数目。select需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

2.epoll不会随着FD的数目增长而降低效率,select采用轮询的方式扫描文件描述符,fd数量越多,性能越差; epoll维护 了一个队列,直接看队列是不是空就可以了。epoll只会对“活跃”的socket进行操作。这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的,那么只有活跃socket才会主动去调用callback函数(把这个句柄加入队列)

3.无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,epoll使用mmap减少复制开销,加速内核与用户空间的消息传递。

4.poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,但没有最大连接数限制(基于链表来存储的)

上一篇下一篇

猜你喜欢

热点阅读