1. 程式人生 > 其它 >Socket程式設計(高併發TCP/UDP)

Socket程式設計(高併發TCP/UDP)

技術標籤:作業系統+網路

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 中

在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;
}