1. 程式人生 > >網路程式設計 第二天 (TCP程式設計)

網路程式設計 第二天 (TCP程式設計)

在網路應用程式程式設計時,伺服器與客戶機的執行邏輯不同,因此伺服器端與客戶端的程式碼也不同。

一、TCP程式設計——伺服器端

使用TCP通訊的伺服器端的程式設計流程如下:

如果需要使用TCP協議建立一個伺服器,則需要以下步驟:

    -建立套接字

    -繫結套接字

    -設定監聽模式

    -接收客戶機的連線請求

    -接收/傳送資料

    -斷開連線

1、建立一個套接字

在網路通訊中通常使用套接字socket進行通訊。若需要進行網路通訊則必須建立一個套接字。使用socket()函式建立一個套接字。

socket()函式的用法:

    函式socket()

    所需標頭檔案:#include<sys/types.h>

                        #include<sys/socket.h>

    函式原型:int socket(int domain, int type, int protocol)

    函式引數:

        domain        選擇通訊協議。通訊協議族定義在標頭檔案socket.h中。常用的協議有:

            AF_INET        IPv4通訊協議

            AF_INET6    IPv6通訊協議

            AF_UNIX        本地通訊

            //其他取值見man幫助文件和標頭檔案socket.h內容

        type        套接字型別。常見的值有:

            SOCK_STREAM    流式套接字,TCP協議選用該套接字型別

            SOCK_DGRAM    資料報套接字,UDP協議選用該套接字型別

            SOCK_RAW    原始套接字,IP/ICMP協議選用該套接字型別

        protocol    協議值,通常情況下為0,即預設IP協議;其他協議值通過檢視:/user/include/linux/in.h

    函式返回值:

        成功:非負套接字檔案描述符

        失敗:-1

    使用scoket()建立套接字是網路程式設計的第一步,也是最重要的一步,後續的所有網路通訊程式設計函式都是基於套接字進行操作的。

    當呼叫socket()之後,核心內部生成一個socket結構體,並返回該結構體的檔案描述符作為返回值以便後續程式碼進行操作。

注意:套接字在Linux核心中是一個結構十分複雜的結構體,使用socket()建立一個套接字會在核心裡生成一個套接字結構體並返回該套接字結構體的檔案描述符。因此絕對不可以使用未獲得socket()返回值的普通的int型別變數進行後續程式設計操作

2、繫結套接字

    使用socket()建立套接字後,該套接字雖然存在但並未給它分配地址,還需將該套接字與指定的IP地址和埠號進行繫結使用bind()給套接字分配地址(IP地址和埠號)

bind()函式的用法:

    函式bind()

    所需標頭檔案:#include<sys/types.h>

                        #include<sys/socket.h>

    函式原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

    函式引數:

        sockfd    需要繫結的套接字的檔案描述符

        addr    繫結的地址。該引數是一個結構體指標,必須使用地址傳遞的方式傳參。需要針對不同的協議設定不同的結構體型別,常見的結構體如下:

struct sockaddr    //通用地址結構體;具體情況下自己還要進行替換

            {

                sa_family_t sa_family;    //地址族,值為AF_XXX

                char sa_data[14];    //協議地址

            }

struct sockaddr_in    //IPv4協議地址結構體,這個才是我們要用到的結構體

            {

                sa_family_t sin_family;    //地址族,固定值AF_INET, IPv4

                in_port_t sin_port;    //埠號,通常使用htons()計算獲得

                struct in_addr sin_addr;    //IP地址,具體結構體見下

            }

            在結構體sockaddr_in中,第三個成員sin_addr是一個in_addr型別的結構體,其結構體如下:

struct in_addr

                {

                    uint32_t s_addr;    //IP地址,為32位無符號數,通常使用inet_addr()計算獲得

                }

        addrlen    地址長度。值為第二個引數addr的結構體長度,通常為sizeof(sockaddr_in)

    函式返回值:

        成功:0

        失敗:-1

    繫結套接字函式bind()將套接字與需要進行網路通訊的地址資訊建立連線。伺服器端必須進行bind()操作,但客戶端可以不手動進行bind(),等到通訊連線後客戶端可以自動進行bind()操作

3、設定監聽模式

使用listen()將套接字標記為被動模式,即作為accept()請求連線接入的套接字。

listen()函式的用法:

    函式listen()

    所需標頭檔案:#include<sys/types.h>

                        #include<sys/socket.h>

    函式原型:int listen(int sockfd, int backlog)

    函式引數:

        sockfd      設定監聽的套接字的檔案描述符,該套接字必須為SOCK_STREAM型別或SOCK_SEQPACKET型別

        backlog    請求佇列的最大請求數。若該佇列已滿,則客戶端會收到ECONNREFUSED錯誤,以便通知客戶端在後續重試連線。該值預設為5

    函式返回值:

        成功:0

        失敗:-1

完成監聽後,伺服器會暫時阻塞,等待客戶端發來連線請求(由客戶端的connect()函式傳送),並使用accept()響應該請求。

4、響應連線請求

伺服器端使用accept()函式響應客戶端的連線請求

accept()函式的用法:

    函式accept()

    所需標頭檔案:#include<sys/types.h>

                        #include<sys/socket.h>

    函式原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

    函式引數:

        sockfd    套接字檔案描述符,該套接字必須由socket()建立,bind()繫結,listen()設定監聽

        addr       儲存客戶端的地址資訊,結構體型別見bind()第二個引數。

                必須使用地址傳遞的方式傳參,或設定為NULL。若設定為NULL則表示不儲存客戶端地址資訊,並且第三個引數addrlen也必須為NULL

        addrlen    儲存客戶端addr結構體的長度,單位為位元組,必須使用地址傳遞的方式傳參。若第二個引數addr設定為NULL則該引數也必須為NULL

    函式返回值:

        成功:建立好連線的套接字檔案描述符

        失敗:-1

5、接收資料與傳送資料

在使用socket()、bind()、listen()和accept()後,伺服器端與客戶端已經成功的建立了通訊,此時可以使用send()向對方傳送資料,使用recv()接收對方傳送的資料。

//除了send()/recv(),有時也可以使用read()/write()來接收和傳送資料。其中write()可以取代send(),read()可以取代recv()。使用send()/recv()還是read()/write()全憑程式設計師個人習慣,但是通篇程式碼使用要統一。

send()函式的用法:

    函式send()

    所需標頭檔案:#include<sys/types.h>

                        #include<sys/socket.h>

    函式原型:int send(int sockfd, const void *buf, size_t len, int flags)

    函式引數:

        sockfd    需要傳送資料的套接字檔案描述符

        buf        傳送資料的緩衝區首地址

        len        傳送資料的緩衝區長度

        flags     通常設定為0。若flags設定為0,則send()與write()等價。

    函式返回值:

        成功:實際傳送的位元組數

        失敗:-1

recv()函式的用法:

    函式recv()

    所需標頭檔案:#include<sys/types.h>

                        #include<sys/socket.h>

    函式原型:int recv(int sockfd, void *buf, size_t len, int flags)

    函式引數:

        sockfd    需要接收資料的套接字檔案描述符

        buf         接收資料的緩衝區首地址

        len          接收資料的緩衝區長度

        flags       通常設定為0。若flags設定為0,則recv()與read()等價。

    函式返回值:

        成功:實際接收的位元組數

        失敗:-1

若呼叫recv()後暫時未收到對方send()的資料,則recv()會阻塞等待直至資料到達,如果傳送方關閉recv()會立刻返回0

6、關閉套接字

若通訊雙方通訊完畢,則需要調用close()關閉雙方通訊

函式close()的用法:

    函式close()

    所需標頭檔案:#include<unistd.h>

    函式原型:int close(int sockfd);

    函式引數:

        sockfd    套接字檔案描述符

    函式返回值:

        成功:0

        失敗:-1

使用TCP通訊時連線是雙向的(同時可讀寫),當我們使用close()時會將讀寫通道都關閉。有時我們只希望關閉一個通道(只讀/只寫),此時可以使用shutdown()來關閉通道。

函式shutdown()的用法:

    函式shutdown()

    所需標頭檔案:#include<sys/socket.h>

    函式原型:int shutdown(int sockfd, int how);

    函式引數:

        sockfd    套接字檔案描述符

        how        關閉套接字的指定方向,具體取值如下:

            SHUT_RD      0    關閉讀,之後無法再繼續接收資料

            SHUT_WR      1    關閉寫,之後無法再繼續傳送資料

            SHUT_RDWR 2    同時關閉讀寫此時同close()

    函式返回值:

        成功:0

        失敗:-1

二、TCP程式設計——客戶端

使用TCP通訊的客戶端的程式設計流程如下:

與伺服器端類似,如果需要使用TCP協議建立一個訪問TCP伺服器的客戶機,需要以下步驟:

    -建立套接字

    -繫結套接字(可選)

    -向伺服器傳送連線請求

    -接收/傳送資料

    -斷開連線

//上文出現過的函式不再講解

1、向伺服器傳送連線請求

客戶端使用connect()向TCP伺服器傳送連線請求伺服器端使用accept()接收該請求

函式connect()的用法:

    函式connect()

    所需標頭檔案:#include<sys/types.h>

                        #include<sys/socket.h>

    函式原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

    函式引數:

        sockfd    需要傳送資料的套接字檔案描述符

        addr    傳送資料的結構體,必須使用地址傳遞傳參。結構體定義見bind()

        addrlen    儲存addr結構體的長度

    函式返回值:

        成功:0

        失敗:-1

以上就是TCP程式設計需要的所有系統呼叫函式。除了這些之外,還需要其他的一些函式。

2、IP地址轉換函式

    通常情況下,我們輸入的IP地址為字串,但是網路連線時需要具體的數值。因此需要使用IP地址轉換函式將字串的IP地址轉換成數值型別的IP地址。

    IP地址轉換函式有3個:inet_addr()、inet_pton()、inet_ntop()。其中inet_addr()只能用於IPv4協議,而inet_pton()和inet_ntop()既可以用於IPv4協議,又可以用於IPv6協議。由於本章以IPv4為主,因此只使用inet_addr()。

    inet_addr()與inet_pton()用於將字串型別的IP地址轉換成數字型別的IP地址,而inet_ntop()則是將數字型別的IP地址轉換成字串型別的IP地址

函式inet_addr()、inet_pton()和inet_ntop()的用法:

    函式inet_addr()

    所需標頭檔案:#include<arpa/inet.h>

    函式原型:int inet_addr(const char *cp);

    函式引數:

        cp    需要轉換的IP地址的字串(IPv4,點分十進位制數)

    函式返回值:

        成功:32位二進位制IP地址

        失敗:-1

注意:inet_addr()只能用於IPv4協議

    函式inet_pton()

    所需標頭檔案:#include<arpa/inet.h>

    函式原型:int inet_pton(int af, const char *src, void *dst);

    函式引數:

        af    需要轉換的IP地址的協議,有兩個取值:

            AF_INET        IPv4通訊協議

            AF_INET6    IPv6通訊協議    

        src    需要轉換的IP地址的字串

        dst    轉換後的資料儲存位置的首地址

    函式返回值:

        成功:0

        失敗:-1

    函式inet_ntop()

    所需標頭檔案:#include<arpa/inet.h>

    函式原型:const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

    函式引數:

        af     需要轉換的IP地址的協議,有兩個取值:

            AF_INET        IPv4通訊協議

            AF_INET6    IPv6通訊協議    

        src     需要轉換的二進位制IP地址

        dst     轉換後的字串儲存位置的首地址,必須不能為NULL

        size dst的可用大小

    函式返回值:

        成功:dst地址(非空)

        失敗:NULL

3、位元組序轉換函式

由於現代的計算機中有兩種位元組序——大端序和小端序,且兩種位元組序儲存資料的方式完全不同,因此需要位元組序轉換。

大端序通常用於網路通訊,而小端序通常用於作業系統內部。因此每次在傳送資料前和接收資料後都需要使用位元組序轉換函式將資料進行轉換。

位元組序轉換函式有4個:htons()、ntohs()、htonl()、ntohl()。其中:

    h    代表host,即本地

    n    代表network,即網路

    s    代表short,即短資料,通常用於埠號轉換

    l    代表long,即長資料,通常用於IP地址轉換

函式htons()、ntohs()、htonl()、ntohl()的用法:

    函式htons()、ntohs()、htonl()、ntohl()

    所需標頭檔案:#include<arpa/inet.h>

    函式原型:

        uint16_t htons(uint16_t hostshort);

        uint16_t ntohs(uint16_t netshort);

        uint32_t htonl(uint32_t hostlong);

        uint32_t ntohl(uint32_t netlong);

    函式引數:

        hostshort    主機位元組序資料(16位無符號)

        netshort    網路位元組序資料(16位無符號)

        hostlong    主機位元組序資料(32位無符號)

        netlong        網路位元組序資料(32位無符號)

    函式返回值:

        成功:轉換後的資料

        失敗:-1

示例:使用TCP進行通訊,程式碼分成伺服器端和客戶端兩部分

//伺服器端

//檔案server.c    

#include<stdio.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#include<arpa/inet.h>

#define BUFFER 128

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

{

    int listenfd,connfd;//通訊用套接字,其中listenfd用於伺服器本身建立連線用套接字,connfd用於accept()函式返回用套接字

    struct sockaddr_in servaddr,cliaddr;//儲存地址與埠號結構體,其中servaddr用於伺服器資訊,cliaddr用於accept()傳參儲存客戶機資訊

    //由於使用IPv4方式,所以直接定義sockaddr_in型別結構體,在傳參時直接進行強制型別轉換即可

    socklen_t peerlen;//結構體大小

    char buf[BUFFER];//儲存資訊緩衝區

    if(argc<3)

    {

        printf("too few argument\n");

        printf("Usage: %s <ip> <port>\n",argv[1]);

        exit(0);

    }

    /*1.建立套接字socket()*/

    if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)

    {

        perror("socket");

        exit(0);

    }

    printf("listenfd is %d\n",listenfd);//建立成功

    /*1.5.設定servaddr成員,準備繫結bind()*/

    bzero(&servaddr,sizeof(servaddr));

    servaddr.sin_family = AF_INET;//協議:IPv4

    servaddr.sin_port = htons(atoi(argv[2]));//埠號,由argv[2]給定

    servaddr.sin_addr.s_addr = inet_addr(argv[1]);//IP地址:由argv[1]給定

    /*2.繫結套接字bind()*/

    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)//注意強制型別轉換與地址傳遞

    {

        perror("bind");

        exit(0);

    }

    printf("bind success!\n");//繫結成功

    /*3.設定監聽listen()*/

    if(listen(listenfd,5)<0)

    {

        perror("listen");

        exit(0);

    }

    printf("Listening...\n");//監聽成功

    peerlen = sizeof(cliaddr);

    while(1)//使用迴圈,讓伺服器不會立即退出

    {

    /*4.使用accept()接收客戶機連線請求*/

        if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&peerlen))<0)

        {

            perror("accept");

            exit(0);

        }

        memset(buf,0,sizeof(buf));

    /*5.使用recv()接收客戶機資訊*/

        if(recv(connfd,buf,BUFFER,0)<0)

        {

            perror("recv");

            exit(0);

        }

        printf("Received a message:%s\n",buf);//接收成功並列印

        strcpy(buf,"Welcome to server");

    /*6.使用send()向客戶機發送資訊*/

        send(connfd,buf,BUFFER,0);

    /*7.關閉連線*/

        close(connfd);

    }

    close(listenfd);

    return 0;

}
//客戶端

//檔案client.c

#include<stdio.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#include<arpa/inet.h>

#define BUFFER 128

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

{

    int sockfd;//通訊用套接字

    char buf[BUFFER]="Hello World";//儲存資訊緩衝區

    struct sockaddr_in myaddr;//儲存地址與埠號結構體

    if(argc<3)

    {

        printf("too few argument\n");

        printf("Usage: %s <ip> <port>\n",argv[0]);

        exit(0);

    }

    /*1.建立套接字socket()*/

    if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)

    {

        perror("socket");

        exit(0);

    }

    /*1.5.設定myaddr成員*/

    bzero(&myaddr,sizeof(myaddr));

    myaddr.sin_family = AF_INET;

    myaddr.sin_port = htons(atoi(argv[2]));

    myaddr.sin_addr.s_addr = inet_addr(argv[1]);

    /*2.使用connect()與伺服器進行連線*/

    if(connect(sockfd,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)//若未開啟伺服器,則這步會報錯

    {

        perror("connect");

        exit(0);

    }

    /*3.使用send()傳送資訊*/

    send(sockfd,buf,sizeof(buf),0);

    /*4.使用recv()接收伺服器資訊*/

    if(recv(sockfd,buf,sizeof(buf),0)<0)

    {

        perror("recv");

        exit(0);

    }

    printf("recv from server: %s\n",buf);

    /*5.關閉連線*/

    close(sockfd);

    return 0;

}

練習1:使用TCP協議連線伺服器端與客戶端,客戶端可以不斷向伺服器端傳輸資訊,直至輸入特定資訊(例如"byebye")伺服器才會與客戶端斷開連線

答案:

//檔案server.c

#include<stdio.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#include<arpa/inet.h>

#define BUFFER 128

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

{

    int listenfd,connfd;

    struct sockaddr_in servaddr,cliaddr;

    socklen_t peerlen;

    char buf[BUFFER];

    if(argc<3)

    {

        printf("too few argument\n");

        printf("Usage: %s <ip> <port>\n",argv[1]);

        exit(0);

    }

    if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)

    {

        perror("socket");

        exit(0);

    }

    printf("listenfd is %d\n",listenfd);

    bzero(&servaddr,sizeof(servaddr));

    servaddr.sin_family = AF_INET;

    servaddr.sin_port = htons(atoi(argv[2]));

    servaddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)

    {

        perror("bind");

        exit(0);

    }

    printf("bind success!\n");

    if(listen(listenfd,5)<0)

    {

        perror("listen");

        exit(0);

    }

    printf("Listening...\n");

    peerlen = sizeof(cliaddr);

    if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&peerlen))<0)

    {

        perror("accept");

        exit(0);

    }

    printf("Connect:%d\n",connfd);

    while(1)

    {

        memset(buf,0,sizeof(buf));

        if(recv(connfd,buf,BUFFER,0)<0)

        {

            perror("recv");

            exit(0);

        }

        printf("Received a message:%s",buf);

        //printf("[%s:%d] %s",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port),buf);

        if(strncmp(buf,"byebye",6)==0)//指定斷開連線資訊為"byebye"

        {

            printf("Disconnect:%d\n",connfd);

            close(connfd);

            break;

        }

    }

    close(listenfd);

    return 0;

}
//檔案client.c

#include<stdio.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#include<arpa/inet.h>

#define BUFFER 128

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

{

    int sockfd;

    char buf[BUFFER];

    struct sockaddr_in myaddr;

    if(argc<3)

    {

        printf("too few argument\n");

        printf("Usage: %s <ip> <port>\n",argv[0]);

        exit(0);

    }

    if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)

    {

        perror("socket");

        exit(0);

    }

    bzero(&myaddr,sizeof(myaddr));

    myaddr.sin_family = AF_INET;

    myaddr.sin_port = htons(atoi(argv[2]));

    myaddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(connect(sockfd,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)

    {

        perror("connect");

        exit(0);

    }

    while(1)

    {

        memset(buf,0,sizeof(buf));

        fgets(buf,BUFFER,stdin);

        send(sockfd,buf,sizeof(buf),0);

        if(strncmp(buf,"byebye",6)==0)//指定斷開連線資訊為"byebye"

        {

            printf("Client will exit\n");

            close(sockfd);

            break;

        }

    }

    return 0;

}

執行這個練習程式碼時,可以嘗試不開啟伺服器端,直接執行資料端傳送資料。可以發現客戶端是無法啟動的(Connection refused訪問被拒絕)。

這是因為TCP協議是可靠連線,在通訊之前必須在伺服器端與客戶端建立可靠連線。

//練習2:練習檔案IO+網路程式設計

練習2:在伺服器端有一個存放成績的檔案score.txt

學號    姓名    成績

10101    Liu        98

10102    Zhao    97

10103    Sun        92

10104    Chen    97

10105    Tian    95

10106    Li        99

10107    Xiao    96

10108    Meng    94

10109    Lion    91

10110    Bunk    97

客戶端向伺服器端傳送學號,伺服器端查詢檔案內容並返回結果。若有該學號資訊,則返回成績;若無該學號資訊,則返回#代表查詢失敗

答案:

//檔案server.c

#include<stdio.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#include<arpa/inet.h>

#define BUFFER 128

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

{

    int listenfd,connfd;

    FILE *scorefd;

    struct sockaddr_in servaddr,cliaddr;

    socklen_t peerlen;

    int flag = 0;

    char buf[BUFFER],recvbuf[BUFFER]={0};

    if(argc<3)

    {

        printf("too few argument\n");

        printf("Usage: %s <ip> <port>\n",argv[1]);

        exit(0);

    }

    if((scorefd=fopen("score.txt","r+"))==NULL)

    {

        perror("cannot open score.txt");

        exit(0);

    }

    if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)

    {

        perror("socket");

        exit(0);

    }

    printf("listenfd is %d\n",listenfd);

    bzero(&servaddr,sizeof(servaddr));

    servaddr.sin_family = AF_INET;

    servaddr.sin_port = htons(atoi(argv[2]));

    servaddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)

    {

        perror("bind");

        exit(0);

    }

    printf("bind success!\n");

    if(listen(listenfd,5)<0)

    {

        perror("listen");

        exit(0);

    }

    printf("Listening...\n");

    peerlen = sizeof(cliaddr);

    if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&peerlen))<0)

    {

        perror("accept");

        exit(0);

    }

    printf("Connect:%d\n",connfd);

    memset(buf,0,sizeof(buf));

    if(recv(connfd,recvbuf,BUFFER,0)<0)

    {

        perror("recv");

        exit(0);

    }

    printf("Received a number:%s",recvbuf);

    while(fgets(buf,BUFFER,scorefd)!=NULL)//查詢檔案內容

    {

        if(strncmp(buf,recvbuf,5)==0)//如果發現了目標學號迴圈停止

        {

            flag = 1;

            break;

        }

    }

    if(flag==1)//找到

    {

        send(connfd,buf,sizeof(buf),0);//傳送給客戶端

    }

    else//未找到

    {

        memset(buf,0,sizeof(buf));

        sprintf(buf,"#");

        send(connfd,buf,sizeof(buf),0);//給客戶端傳送#表示查詢失敗

    }

    close(connfd);

    close(listenfd);

    fclose(scorefd);

    return 0;

}
//檔案client.c

#include<stdio.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#include<arpa/inet.h>

#define BUFFER 128

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

{

    int sockfd;

    char buf[BUFFER];

    struct sockaddr_in myaddr;

    if(argc<3)

    {

        printf("too few argument\n");

        printf("Usage: %s <ip> <port>\n",argv[0]);

        exit(0);

    }

    if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)

    {

        perror("socket");

        exit(0);

    }

    bzero(&myaddr,sizeof(myaddr));

    myaddr.sin_family = AF_INET;

    myaddr.sin_port = htons(atoi(argv[2]));

    myaddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(connect(sockfd,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)

    {

        perror("connect");

        exit(0);

    }

    memset(buf,0,sizeof(buf));

    printf("Please input number:");

    fgets(buf,BUFFER,stdin);

    send(sockfd,buf,sizeof(buf),0);

    memset(buf,0,sizeof(buf));

    if(recv(sockfd,buf,sizeof(buf),0)<0)

    {

        perror("recv");

        exit(0);

    }

    if(strncmp("#",buf,1)!=0)

    {

        printf("recv from server:\n%s",buf);

    }

    else

    {

        printf("No result!\n");

    }

    return 0;

}

//練習3:練習執行緒程式設計+網路程式設計

練習3:使用多執行緒程式設計,實現伺服器端與客戶端的雙向通訊,即伺服器端與客戶端可以同時接收和傳送資料。當客戶端輸入"byebye"時伺服器端與客戶端斷開連線

//注意:本題使用程序程式設計需要程序通訊手段,程式碼難度大幅度上升,因此不推薦使用程序程式設計

//伺服器端

//檔案server_thread.c

//注意:由於伺服器接收到"byebye"後需要與客戶端斷開連線,因此在主程序內接收資料而線上程內傳送資料

#include<stdio.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#include<pthread.h>

#include<arpa/inet.h>

#define BUFFER 128

void *thread_send(void *arg)

{

    int newsockfd = *((int*)arg);

    char sendbuffer[BUFFER]={0};

    while(1)

    {

        fgets(sendbuffer,BUFFER,stdin);

        send(newsockfd,sendbuffer,BUFFER,0);

    }

    pthread_exit(NULL);//可以省略

}

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

{

    int listenfd,connfd;

    struct sockaddr_in servaddr,cliaddr;

    socklen_t peerlen;

    char buf[BUFFER];

    pthread_t sendtid;

    if(argc<3)

    {

        printf("too few argument\n");

        printf("Usage: %s <ip> <port>\n",argv[1]);

        exit(0);

    }

    if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)

    {

        perror("socket");

        exit(0);

    }

    printf("listenfd is %d\n",listenfd);

    bzero(&servaddr,sizeof(servaddr));

    servaddr.sin_family = AF_INET;

    servaddr.sin_port = htons(atoi(argv[2]));

    servaddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)

    {

        perror("bind");

        exit(0);

    }

    printf("bind success!\n");

    if(listen(listenfd,10)<0)

    {

        perror("listen");

        exit(0);

    }

    printf("Listening...\n");

    peerlen = sizeof(cliaddr);

    if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&peerlen))<0)

    {

        perror("accept");

        exit(0);

    }

    printf("Connect:[%s:%d]\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));

    if((pthread_create(&sendtid,NULL,thread_send,&connfd))!=0)

    {

        perror("create thread");

        exit(0);

    }

    while(1)

    {

        if((recv(connfd,buf,BUFFER,0))<0)

        {

            perror("recv");

            exit(0);

        }

        printf("Received:%s",buf);

        if(strncmp(buf,"byebye",6)==0)

        {

            break;

        }

    }

    close(connfd);

    close(listenfd);

    return 0;

}
//客戶端

//檔案client_thread.c

//注意:由於客戶端傳送"byebye"後與伺服器端斷開連線,因此在主程序內傳送資料而線上程內接收資料

#include<stdio.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<pthread.h>

#define BUFFERSIZE 128

void *thread_recv(void *arg)

{

    int listenfd=*((int*)arg);

    char recvbuf[BUFFERSIZE]={0};

    while(1)

    {

        if(recv(listenfd,recvbuf,BUFFERSIZE,0)<0)

        {

            perror("recv");

            exit(0);

        }

        printf("recv:%s",recvbuf);

        bzero(recvbuf,BUFFERSIZE);

    }

    pthread_exit(NULL);//可以省略

}

int main()

{

    int sockfd;

    pthread_t pthrecv;

    char buf[BUFFERSIZE]={0};

    struct sockaddr_in servaddr;

    if(argc<3)

    {

        printf("too few argument\n");

        printf("Usage: %s <ip> <port>\n",argv[1]);

        exit(0);

    }

    //建立socket

    if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)

    {

        perror("socket");

        exit(0);

    }

    //設定sockaddr_in結構體中相關引數

    bzero(&servaddr,sizeof(servaddr));

    servaddr.sin_family = AF_INET;

    servaddr.sin_port = htons(atoi(argv[2]));

    servaddr.sin_addr.s_addr = inet_addr(argv[1]);

    //呼叫connect()函式向伺服器端建立TCP連結

    if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)

    {

        perror("connect");

        exit(0);

    }

    //建立執行緒,用於接收資訊

    if((pthread_create(&pthrecv,NULL,thread_recv,&sockfd))!=0)

    {

        perror("建立執行緒");

        exit(0);

    }

    //傳送訊息給伺服器端

    while(1)

    {

        fgets(buf,BUFFERSIZE,stdin);

        send(sockfd,buf,BUFFERSIZE,0);

        if(strncmp(buf,"byebye",6)==0)

            break;

    }

    close(sockfd);

    return 0;

}