网络编程套接字二--TCP(多进程与多线程)

一、TCP服务器

1、socket()
参数:
对于IPv4, family参数指定为AF_INET;
对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
protocol参数的介绍从略,指定为0即可。

返回值:
socket()打开一个网络通讯端口,
如果成功的话,就像open()一样返回一个文件描述符; 应用程序可以像读写文件一样用read/write在网络上收发数据;
如果socket()调用出错则返回-1

2、bind()

功能:
bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端⼝口号。
参数:

上一篇文章讲过,struct sockaddr *是一个通用指针类型,

myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度

返回值:

bind()成功返回0,失败返回-1。

3、listen():实现监听

参数:

sockfd:listen()声明sockfd处于监听状态,

backlog:最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大
返回值:成功返回0,失败返回-1

4、accept():获得连接并建立连接

参数:

addr是一个传出参数,accept()返回时传出客户端的地址和端口号。传NULL,表示不关心客户端的地址;
addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)

如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来


5、简单实现TCP网络程序服务器:
利用以上函数:
 
  
  1 #include<stdio.h>
  2 #include<errno.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<sys/types.h>
  6 #include<sys/socket.h>
  7 #include<netinet/in.h>
  8 
  9 #define _PORT_ 8080
 10 #define _BACKLOG_ 10
 11 
 12 int main(){
 13     int sock=socket(AF_INET,SOCK_STREAM,0);
 14     if(sock<0){
 15         printf("create socket error,error is : %d,errstring is : %s\n",errno,strerror(errno));
 16     }
 17 
 18     struct sockaddr_in server_socket;
 19     struct sockaddr_in client_socket;
 20     bzero(&server_socket,sizeof(server_socket));
 21     server_socket.sin_family=AF_INET;
 22     server_socket.sin_addr.s_addr=htonl(INADDR_ANY);
 23     server_socket.sin_port=htons(_PORT_);
 24 
 25     if(bind(sock,(struct sockaddr*)&server_socket,sizeof(struct sockaddr_in))<0){
 26         printf("bind error,error code is : %d,error string is : %s\n",errno,strerror(errno));
 27         close(sock);
 28         return 1;
 29     }
 30     if(listen(sock,_BACKLOG_)<0){
 31         printf("listen error,error code is : %d,error string is : %s\n",errno,strerror(errno));
 32         close(sock);
 33         return 2;
 34     }
 35     printf("bind and listen success,wait accept...\n");
 36     for(;;){
 37         socklen_t len=0;
 38         int client_sock=accept(sock,(struct sockaddr*)&client_socket,&len);
 39         if(client_sock<0){
 40             printf("accept error,errno is : %d,errstring is:%s\n",errno,strerror(errno));
 41             close(sock);
 42             return 3;
 43         }
 44         char buf_ip[INET_ADDRSTRLEN];
 45         memset(buf_ip,'\0',sizeof(buf_ip));
 46         inet_ntop(AF_INET,&client_socket.sin_addr,buf_ip,sizeof(buf_ip));
 47 
 48         printf("get connect,ip is : %s port is : %d\n",buf_ip,ntohs(client_socket.sin_port));
 49         while(1){
 50             char buf[1024];
 51             memset(buf,'\0',sizeof(buf));
 52 
 53             read(client_sock,buf,sizeof(buf));
 54             printf("client :#%s\n",buf);
 55 
 56             printf("server :$ ");
 57 
 58             memset(buf,'\0',sizeof(buf));
 59             fgets(buf,sizeof(buf),stdin);
 60             buf[strlen(buf)-1]='\0';
 61             write(client_sock,buf,strlen(buf)+1);
 62             printf("please wait...\n");
 63         }
 64     }
 65     close(sock);
 66     return 0;
 67 }
 68 

二、TCP客户端

1、connect():客户端建立连接
connect()连接服务器:

参数:
connect和bind的参数形一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址
返回值:
成功返回0,出错返回-1

2、关于bind()
客户端没有必要调用bind()固定一个端口号,否则如果在同一台机器上启动多个客户端,就会出现端口号被占用导致不能正确建立连接;
服务器也不是必须调用bind(), 但如果服务器不调用bind(), 内核会自动给服务器分配监听端口, 每次启动服务器时端口号都不一样, 客户端要连接服务器就会遇到麻烦。

3、简单实现TCP网络程序客户端:

#include<stdio.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
 
#define SERVER_PORT 8080
#define SERVER_IP "127.0.0.1"

int main(int argc,char *argv[]){
    if(argc!=2){
        printf("Usage : client IP\n");
        return 1;
    }
    char *str=argv[1];
    char buf[1024];
    memset(buf,'\0',sizeof(buf));

    struct sockaddr_in server_sock;

    int sock=socket(AF_INET,SOCK_STREAM,0);
    bzero(&server_sock,sizeof(server_sock));
    server_sock.sin_family=AF_INET;
    //inet_pton(AF_INET,SERVER_IP,&server_sock.sin_addr);
    server_sock.sin_port=htons(SERVER_PORT);
    server_sock.sin_addr.s_addr=inet_addr(SERVER_IP);

    int ret=connect(sock,(struct sockaddr *)&server_sock,sizeof(server_sock));
    if(ret<0){
        printf("connect failed...,errno is : %d,errstring is : %s\n",errno,strerror(errno));
        return 1;
    }
    printf("connect success...\n");
    while(1){
        printf("client : # ");
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1]='\0';
        write(sock,buf,sizeof(buf));
        if(strncasecmp(buf,"quit",4)==0){
            printf("quit!\n");
            break;
        }
        printf("please wait...\n");
        read(sock,buf,sizeof(buf));
        printf("server :$ %s\n",buf);
    }
    close(sock);
    return 0;
}
测试:
运行服务器:

我们用netstat查看一下监听状态:
可以看到tcp_server程序监听8080端口。
运行客户端,进程通信成功。

三、实现简单的TCP多进程网络程序

1、实现多进程TCP,我们就要调用系统函数fork来创建进程。
通过每个请求, 创建子进程的方式来支持多连接。我们可以让父进程监听客户端的请求连接,子进程对已经建立连接的客户端提供服务。

服务器端代码如下:

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

void Usage(){
    printf("usage : ./server [ip][port]\n");
}

void ProcessRequest(int client_fd,struct sockaddr_in* client_addr){
    char buf[1024]={0};
    for(;;){
        ssize_t read_size=read(client_fd,buf,sizeof(buf));
        if(read_size<0){
            perror("read");
            continue;
        }
        if(read_size==0){
            printf("client: %s say bye!\n",inet_ntoa(client_addr->sin_addr));
            close(client_fd);
            break;
        }
        buf[read_size]='\0';
        printf("client %s say: %s\n",inet_ntoa(client_addr->sin_addr),buf);
        write(client_fd,buf,strlen(buf));
    }
}
 
void CreateWorker(int client_fd,struct sockaddr_in* client_addr){
    pid_t pid=fork();
    if(pid<0){
        perror("fork");
        return;
    }else if(pid==0){
         //child
        if(fork()==0){
            //grand_child
            ProcessRequest(client_fd,client_addr);
        }
        exit(0);
    }
    else{
         //father
        close(client_fd);
        waitpid(pid,NULL,0);
    }
}

int main(int argc,char* argv[]){
    if(argc!=3){
        Usage();
        return 1;
    }
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=inet_addr(argv[1]);
    addr.sin_port=htons(atoi(argv[2]));

    int fd=socket(AF_INET,SOCK_STREAM,0);
    if(fd<0){
        perror("socket");
        return 1;
    }
    int ret=bind(fd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret<0){
        perror("bind");
        return 1;
    }
    ret=listen(fd,10);
    if(ret<0){
        perror("listen");
        return 1;
    }
 
    for(;;){
        struct sockaddr_in client_addr;
        socklen_t len=sizeof(client_addr);
        int client_fd=accept(fd,(struct sockaddr*)&client_addr,&len);
        if(client_fd<0){
            perror("accept");
            continue;
        }
        char bufip[32];
        bufip[0]=0;
        inet_ntop(AF_INET,&client_addr.sin_addr,bufip,sizeof(bufip));
        printf("get connect,ip is:%s port is:%d\n",bufip,ntohs(client_addr.sin_port));
        CreateWorker(client_fd,&client_addr);
    }
    close(fd);
    return 0;
}

2、优缺点
缺点:
(1)只有链接的请求到达,才会创建进程,影响性能。可以利用进程池提前创建好。

(2)多进程服务器同时服务的人数有上限,且十分占用资源

(3)进程增多会导致切换成本太大,进而影响性能。

优点:

(1)可以处理多用户

(2)编写简单
(3)稳定。若一进程挂掉,不会影响其他进程。

四、实现简单的TCP多线程网络程序
1、通过每个请求,创建一个线程的方式来支持多连接:

要实现多线程的TCP,我们必须调用pthread_create函数创建新线程。多线程TCP需要创建线程就可以了,与多进程主逻辑不变。

服务器端代码如下:

#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<netinet/in.h>
#include<pthread.h>
 
void Usage(){
    printf("usage : ./server [ip][port]\n");
}

void ProcessRequest(int client_fd,struct sockaddr_in* client_addr){
    char buf[1024]={0};
    for(;;){
        ssize_t read_size=read(client_fd,buf,sizeof(buf));
        if(read_size<0){
            perror("read");
            continue;
        }
        if(read_size==0){
            printf("client: %s say bye!\n",inet_ntoa(client_addr->sin_addr));
            close(client_fd);
            break;
        }
        buf[read_size]='\0';
        printf("client %s say: %s\n",inet_ntoa(client_addr->sin_addr),buf);
        write(client_fd,buf,strlen(buf));
    }
}
 
typedef struct ARG{
    int fd;
    struct sockaddr_in addr;
}Arg;
 
void* CreateWorker(void *ptr){
    Arg* arg=(Arg*)ptr;
    ProcessRequest(arg->fd,&arg->addr);
    free(arg);
    return NULL;
}
 
int main(int argc,char* argv[]){
    if(argc!=3){
        Usage();
        return 1;
    }
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=inet_addr(argv[1]);
    addr.sin_port=htons(atoi(argv[2]));

    int fd=socket(AF_INET,SOCK_STREAM,0);
    if(fd<0){
        perror("socket");
        return 1;
    }
    int ret=bind(fd,(struct sockaddr*)&addr,sizeof(addr));

    printf("qqq");
    if(ret<0){
       perror("bind");
        return 1;
    }

    printf("qqq");
    ret=listen(fd,10);
    if(ret<0){
        perror("listen");
        return 1;
    }

    for(;;){
       struct sockaddr_in client_addr;
       socklen_t len=sizeof(client_addr);
        int client_fd=accept(fd,(struct sockaddr*)&client_addr,&len);
        if(client_fd<0){
            perror("accept");
            continue;
        }
        char bufip[32];
        bufip[0]=0;
        inet_ntop(AF_INET,&client_addr.sin_addr,bufip,sizeof(bufip));
        printf("get connect,ip is : %s port is :%d\n",bufip,ntohs(client_addr.sin_port));
        pthread_t tid=0;
        Arg* arg=(Arg*)malloc(sizeof(Arg));
        arg->fd=client_fd;
        arg->addr=client_addr;
        pthread_create(&tid,NULL,CreateWorker,(void*)arg);
        pthread_detach(tid);
    }
    close(fd);
    return 0;
}
                                    

2、优缺点
与多进程比较,多线程只是其的一个缓解,在性能、占用资源与切换成本等方面,都有了一定的提高。
但是多了一个缺点,不稳定,若有一个线程出现异常,则所有的线程挂掉。

注意: 在创建子进程后,子进程必须回收,否则就会造成内存泄漏。
新线程在退出后我们也必须回收,否则会造成资源浪费

猜你喜欢

转载自blog.csdn.net/baidu_37964071/article/details/80199531