欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Socket编程(高并发TCP/UDP)

程序员文章站 2022-03-07 20:26:49
...

                             Socket编程(TCP/UDP) <sys/socket.h>

如有问题请多多指教,

 

1.Tcp模式(由客户端链接服务器)

2.UDP模式:

3.多线程tcp

4.多进程tcp


首先我们需要知道tcp和udp的协议的特点。

1.Tcp模式(由客户端链接服务器)

服务器端                                                       客户端

socket() 创建套接字                      socket()创建套接字

bind() 命名套接字(绑定地址)            

listen()创建监听队列                    

accept()接收连接(阻塞)             connect()发起连接

recv() 接收数据                             send()发送数据

Send()发送数据                            recv()发送数据

Close()关闭连接                           close()关闭链接

对于每一个主机我们都有大端和小端的区别,为啥保证可以正常通讯,我们网络字节序均使用了大端的方式。现在pc端大多数使用小端模式,因此我们的主机字节序使用小端。

<netinet/in.h>

unsigned long int htonl(unsigned long int hostlong)

unsigned short int htons(unsigned short int hostlong)

unsigned long int ntonl(unsigned long int netlong)

unsigned short int ntons (unsigned short int netshort)

例如:

htonl 表示 host to network long 即将长整型主机字节序的数据转换成网络字节序。

ip地址转换函数

通常,我们习惯用可读性好的字符串来表示ip地址,比如用点分十进制字符串表示IPv4地址,以及用十六进制字符串表示IPv6地址。但编程中我们需要将它们转化成整数方能使用。

用点分十进制字符串表示的IPv4地址和网络字节序整数表示的IPv4地址之间的转换:

#include<arpa/inet.h>

int_addr_t inet_addr(const char * strptr);

int inet_aton(const char * cp,struct in_addr* inp);

char * inet_ntoa(struct in_addr in);

inet_addr 将用点分十进制字符串表示的IPv4地址转换成网络字节序整数表示的IPv4,失败时返回 INADDR_NONE;

inet aton 函数和上面的一样,但是将转化的字符串存放在 inp中,成功返回1,失败返回0.

inet_ntoa 函数将用网络字节序整数表示ipv4 转化成 点分十进制字符串表示的IPv4地址但是要注意的是,该函数中,有静态变量,因此inet_nota是不可重入的。

 

函数介绍:

int socket(int domain, int type, int protocol);

失败返回 -1,成功返回监听套接字

domain  设置套接字协议族, AF_UNIX UNIX本地域协议簇, AF_INET TCP/IPV4协议簇  AF_INET6 TCP/IPV6协议簇

type 设置套接字服务类型  SOCK_STREAM (流服务)   SOCK_UGRAM(数据报)

protocol 一般设置为0,使用默认协议

 

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

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

sockfd是监听套接字描述符,addr是ip地址结构,addrlen是地址长度。

struct sockaddr_in {

        short   sin_family;   //地址族 Socket只能用AF_INET

        u_short sin_port;   //端口

        struct in_addr sin_addr; //IPv4网络地址

        char    sin_zero[8];   //

};

 

int listen(int sockfd, int backlog);

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

sockfd 是监听套接字,backlog 监听队列的最大长度。

 

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd 是监听套接字,addr 是连接的ip地址,addrlen 是长度。

 

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockfd是监听套接字,addr是被连接的ip地址,addrlen 是长度

 

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd是连接套接字,buf是收到的数组,len是长度,flags 是标志位

当返回值为0的时候就代表断开链接。

 

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd是连接套接字,buf是写入的数组,len是长度,flags是标志位

 

int close(int fd);

fd是套接字

 

在tcp中,在没有客户端连接的时候,我们回阻塞在accept()函数,等待有人来连接我们。

代码如下:

ser.c

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

int main()
{
    //创建套接子
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    assert(sockfd !=-1);
    
    //指定ip + port  
    struct sockaddr_in caddr,saddr;// saddr, ser; caddr,cli
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000); //网络字节序列  大端
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    assert(res!=-1);
    
    listen(sockfd,5);
    
    while(1)
    {
        int len = sizeof(caddr);
        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
        if( c < 0)
        {
            continue;    
        }

        printf("accept c = %d\n",c);

        char buff[128] = {0};
        while(1)
        {
            int n = recv(c,buff,127,0);
            if(n==0)
            {
                printf("one cli is over\n");
                break;    
            }
            printf("buff=%s\n",buff);
            send(c,"ok",2,0);
        }
        close(c);
    }
    exit(0);
}
cli.c

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

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    assert(sockfd !=-1);

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    assert(res!=-1);
    while(1)
    {
        char buff[128] = {0};
        printf("input:\n");
        fgets(buff,128,stdin);
        if(strncmp(buff,"end",3)==0)
        {
            break;
        }
        send(sockfd,buff,strlen(buff),0);
        memset(buff,0,128);
        recv(sockfd,buff,127,0);
        printf("buff=%s\n",buff);
        
    }
    close(sockfd);

    exit(0);


}

 

2.UDP模式:

服务器                                                           客户端

socket()创建套接字                            socket()创建套接字

bind()命名套接字

recvfrom() 收到数据                           sendto() 发送数据

sendto()  发送数据                                 recvfrom() 收到数据

close() 关闭                                       close() 关闭

socket()  bind()  close() 函数和 tcp的操作一样,不一样的是 收发数据

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd 是监听套接字,buf是发送的数据,len 是长度, flags 是标志位,

dest_addr是对方的ip地址,addrlen是ip地址的长度。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

sockfd是监听套接字,buf是数组,len 是长度,flags是标志位,src_addr是对方的ip地址,addrlen是ip长度。

代码:

ser.c

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


int main()
{
    int sockfd =  socket(AF_INET,SOCK_DGRAM,0);
    assert(sockfd!=-1);
    
    struct sockaddr_in saddr,caddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int res = bind(sockfd,(struct sockaddr* )&saddr,sizeof(saddr));
    
    while(1)
    {
        int len = sizeof(caddr);
        char buff[128] = {0};
        int n = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
        printf("recv:%s\n",buff);
        sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));    
    }
    exit(0);
}
cli.c

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


int main()
{
    int sockfd =  socket(AF_INET,SOCK_DGRAM,0);
    assert(sockfd!=-1);
    
    struct sockaddr_in saddr,caddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int res = bind(sockfd,(struct sockaddr* )&saddr,sizeof(saddr));
    
    while(1)
    {
        char buff[128]={0};
        printf("input:\n");
        fgets(buff,128,stdin);

        if(strncmp(buff,"end",3)==0)
        {
            break;    
        }

        sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)&saddr,sizeof(caddr));
        memset(buff,0,128);
        int len = sizeof(saddr);
        recvfrom(sockfd,buff,127,0,(struct sockaddr*)&saddr,&len);
        printf("recv:%s\n",buff);
            
    }

    close(sockfd);
    exit(0);
}

思考:

如果我们每次只接收一个字节的数据,如果我们发送hello  tcp  和 udp 的结果是怎样的呢?

在tcp 中

Socket编程(高并发TCP/UDP)

在udp中

Socket编程(高并发TCP/UDP)

 

3.多线程tcp

在tcp 中,当有一个客户端连接服务器的时候,我们会进入到循环的收发过程中,这时候当我们在去链接其他的客户端的时候,就会发生阻塞,一直等待当我们正在收发的客户端断开连接后,才会连接入我们新的客户端。这时候我们可以将连接的过程写道主线程中,每次连接一个那我们就去将这个连接后的操作,交给一个线程去操作,以此保证不阻塞的操作。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int create_socket();
//多线程处理高并发问题

void *fun(void *arg)
{
    int c= (int )arg;
    while(1)
    {
        char buff[128] = {0};
        int n= recv(c,buff,127,0);
        if(n<=0)
        {
            break;
        }

        printf("recv(%d)=%s\n",c,buff);
        send(c,"ok",2,0);
    }

    printf("one cli is over\n");
    close(c);
}
int main()
{
    int sockfd = create_socket();
    assert(sockfd != -1);
    
    while(1)
    {
        struct sockaddr_in caddr;
        int len = sizeof(caddr);
        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
        if(c<0)
        {
            continue;
        }

        pthread_t id;
        pthread_create(&id,NULL,fun,(void *)c);
    }
}

int create_socket()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        return -1;
    }

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res==-1)
    {
        return -1;    
    }

    listen(sockfd,5);

    return sockfd;
}

 

4.多进程tcp

多进程的思想和多线程一样,但是在进程复制的时候,也会复制描述符,我们将没有用的描述符进行关闭,在当我们某一个客户端断开连接的时候,这个进程就要结束,这时候就会变成僵死进程,我们需要用信号进行处理。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>

void sig_fun(int sig)
{
    wait(NULL);
}

int create_socket();
//多进程处理高并发问题
int main()
{
    int sockfd = create_socket();
    assert(sockfd != -1);
    
    signal(SIGCHLD,sig_fun);    
    while(1)
    {
        struct sockaddr_in caddr;
        int len = sizeof(caddr);
        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
        if(c<0)
        {
            continue;
        }
        pid_t pid = fork();
        assert(pid!=-1);

        if(pid == 0)
        {
            close(sockfd);
            while(1)
            {
                char buff[128] = {0};
                int n= recv(c,buff,127,0);
                if(n<=0)
                {
                    break;
                }

                printf("recv=%s\n",buff);
                send(c,"ok",2,0);
            }
            close(c);
            printf("onr cli is over\n");
            exit(0);
        }
        close(c);
    }
}

int create_socket()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        return -1;
    }

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res==-1)
    {
        return -1;    
    }

    listen(sockfd,5);

    return sockfd;
}

 

相关标签: 操作系统+网络