Linux網路程式設計(2)——採用TCP的基本server的實現

Instant_發表於2015-08-10

一個基本的C/S伺服器模型很簡單:         客戶端    <------------------------>    伺服器

簡而言之就是客戶端跟伺服器之間的通話,通話方式一般採用TCP和UDP這兩種。

TCP和UDP區別

1、Tcp提供客戶與伺服器之間的連線。TCP客戶端先與某個給定伺服器建立一個連線,再跨該連線於那個伺服器交換資料,然後終止這個連線。

(連線其實就是一種協商機制,預先定義好了雙方的一些狀態變數,告訴對方諸如序列號和通告視窗大小等狀態資訊)

2、Tcp提供了可靠的傳輸機制,不需要像UDP那樣需要通過應用層來實現可靠性,直接通過協議來實現。傳送資料後等待對方確認,沒有收到確認就繼續重傳與等待,數次重傳失敗後才會放棄。

3、Tcp提供了流量控制,tcp總是告訴對端在任何時刻它一次能夠從對端接收多少位元組的資料。


採用套接字的TCP連線的基本程式模型

就是通過socket的這個函式封裝資料,實現server額client的通訊

客戶端                                          服務端

sokcket                                       socket()          //建立套接字描述符

                                                     bind()             //將伺服器地址和相應套接字描述符繫結

                                                     listen()          //將主動套接字轉換為監聽套接字,該套接字可以接受來自客戶端的請求

connect()   <------------->       accept()      //connect()客戶端連線請求 , accpet()函式解析監聽來的客戶端資訊,

write()       ------------>            read()        //網路讀寫io

read()       <--------------         write()

close()             ------------>          read()

                                                     close()           //close關閉連線


函式介面

1、socket

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
若成功返回非負描述符,若出錯返回-1

domain網路型別(一般預設為AF_INET因特網),type套接字型別(一般預設為SOCK_STREAM),protocol協議號

2、connect

#include<sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
若成功返回0,若出錯返回-1

connect函式試圖與套接字地址為serv_addr的伺服器建立一個因特網連線,addrlen預設為sizeof(sockaddr_in)

3、bind

#include<sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen)
若成功返回0,若出錯返回-1
將伺服器地址和套接字聯絡起來

4、listen

#include<sys/socket.h>
int listen(int sockfd, int backlog);
若成功返回0,若出錯返回-1 


告訴核心,這個是伺服器建立的套接字,不是客戶端的,將主動狀態轉換為被動狀態。

5、accept

#include<sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
若成功返回0,若出錯返回-1 



等待來自於客戶端的連線請求到達監聽描述符listenfd,然後在addr中填寫客戶端的套接字地址,並返回一個已連線描述符,這個描述符可以用來利用Unix I/O函式與客戶端通訊。

套接字地址結構體

<netinet/in.h>
struct in_addr{
    in_addr_t s_addr;            //32bit ipv4 address
}

struct sockaddr_in{
    uint8_t;                          
    sa_family_t sin_family;           // AF_INET
    in_port_t sin_port;               //埠
    struct in_addr sin_addr;
    char sin_zero[8];                //未使用
};

DNS主機條目結構體

通過呼叫gethostbyname和gethostbyaddr函式,從DNS資料庫中檢索任意的主機條目。

<pre name="code" class="cpp">#include <netdb.h>
struct hostent{ char *h_name; // 主機域名 char **h_aliases; int h_addrtype; //主機地址 int h_length; char **h_addr_list;
};
struct hostent* gethostbyname(const char* name);
struct hostent* gethostbyaddr(const char* addr, int len, int domain);


具體程式碼實現:

實現一個簡單的客戶端伺服器模型,並不涉及多執行緒、I/O複用此類

server.c  服務端

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

#define MAX_LISTEN 1024
#define MAX_LINE 1024

int Socket(int domain, int type, int protocol){
    int sockfd = socket(domain, type, protocol);
    if ( sockfd < 0 ){
        perror("init socket:  ");
        exit(0);
    }
    return sockfd;
}

void Bind(int sockfd, struct sockaddr *myaddr, int addrlen){
    if ( bind(sockfd, myaddr, addrlen) < 0 ){
        perror("bind");
        exit(0);
    } }

void Listen(int sockfd, int backlog){
    if ( listen(sockfd, backlog) < 0){
        perror("listen");
        exit(0);
    }
}

int Accept(int listenfd, struct sockaddr *addr, int *addrlen){
    int clientfd = accept(listenfd, addr, addrlen);
    if ( clientfd < 0){
        perror("accept");
        exit(0);
    }
    return clientfd;
}

void Close(int clientfd){
    if ( close(clientfd) < 0){
        perror("close");
        exit(0);
    }
}
struct hostent* Gethostbyaddr(const char *addr, int len, int domain){
    struct hostent* host = gethostbyaddr(addr, len, domain);
    if ( NULL == host ){
        perror("host_by_addr");
        exit(0);
    }
    return host;
}

ssize_t Read(int fd, void* buf, size_t n){
    ssize_t num= read(fd, buf, n);
    if ( n < 0){
        perror("read");
        exit(0);
    }
    return num;
}

ssize_t Write(int fd, const void* buf, size_t n){
    ssize_t num = read(fd, buf, n);
    if ( n < 0){
        perror("write");
        exit(0);
    }
    return num;
}

void echo(listenfd){
    ssize_t n;
    char write_buff[MAX_LINE];
    char read_buff[MAX_LINE];
    
    memset(write_buff, 0, MAX_LINE);
    memset(read_buff, 0, MAX_LINE);

    n = read(listenfd, read_buff, MAX_LINE);
    read_buff[n] = '\0';

    strcpy(write_buff, "from server echo: ");
    strcpy(write_buff+strlen("from server echo: "), read_buff);

    n = write(listenfd, write_buff, MAX_LINE);

    
}

int main(int argc, char **argv){
    int servfd, clientfd, port, clientlen;
    struct sockaddr_in servaddr;
    struct sockaddr_in cliaddr;
    struct hostent *host;
    char* hostaddr;
    if ( argc != 2){
        fprintf(stderr,"usage:%s<port>\n", argv[0]);
        exit(0);
    }
    port = atoi(argv[1]);  // get port

    servfd = Socket(AF_INET, SOCK_STREAM, 0);
    
    // init servaddr
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons((unsigned short)port);
    clientlen = sizeof(cliaddr);
    
    Bind(servfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    Listen(servfd, MAX_LISTEN);

    while(1){   // init server
        memset(&cliaddr, 0, sizeof(cliaddr));
        clientfd = Accept(servfd, (struct sockaddr*)&cliaddr, &clientlen);
        host = Gethostbyaddr((const char*)&cliaddr.sin_addr.s_addr, sizeof(cliaddr.sin_addr.s_addr), AF_INET);
        printf("server connect to host: %s %s\n",host->h_name, inet_ntoa(cliaddr.sin_addr));
        echo(clientfd);
        Close(clientfd);
    }
}

client.c  客戶端

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

#define MAX_LINE 1024

int Socket(int domain, int type, int protocol){
    int sockfd = socket(domain, type, protocol);
    if ( sockfd < 0 ){
        perror("init socket");
        exit(0);
    }
    return sockfd;
}
void Close(int clientfd){
    if ( close(clientfd) < 0){
        perror("close");
        exit(0);
    }
}
struct hostent* Gethostbyaddr(const char *addr, int len, int domain){
    struct hostent* host = gethostbyaddr(addr, len, domain);
    if ( NULL == host ){
        perror("host_by_addr");
        exit(0);
    }
    return host;
}

ssize_t Read(int fd, void* buf, size_t n){
    if ( read(fd, buf, n) < 0){
        perror("read");
        exit(0);
    }
}

ssize_t Write(int fd, const void* buf, size_t n){
    if ( write(fd, buf, n) < 0){
        perror("write");
        exit(0);
    }
}

void Connect(int sockfd, struct sockaddr* serv_addr, int addrlen){
    if ( connect(sockfd, serv_addr, addrlen) < 0){
        perror("connect");
        exit(0);
    }
}

void message_handle(int clientfd){
    size_t n;
    char send_buff[MAX_LINE];
    char recv_buff[MAX_LINE];
    memset(send_buff, 0, MAX_LINE);
    memset(recv_buff, 0, MAX_LINE);

    fgets(send_buff, MAX_LINE, stdin);
    send_buff[strlen(send_buff)-1] = '\0';

    n = Write(clientfd, send_buff, strlen(send_buff)+1);
    n = Read(clientfd, recv_buff, MAX_LINE);

    printf("%s \n", recv_buff);
}

int main(int argc, char **argv){
    int  clientfd, port;
    struct sockaddr_in servaddr;
    if ( argc != 3){
        fprintf(stderr,"usage:%s<addr> <port>\n", argv[0]);
        exit(0);
    }
    port = atoi(argv[2]);
    printf("port:  %d\n", port);
    printf("addr: %s\n", argv[1]);

    clientfd = Socket(AF_INET, SOCK_STREAM, 0);

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    Connect(clientfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); 
    message_handle(clientfd);
    Close(clientfd);
}



程式碼分析:

服務端按上圖TCP模型建立伺服器處理客戶端連線,每當有客戶端連線時即列印客戶資訊,然後接受到客戶發來的訊息後,將客戶訊息反射回客戶端。

執行結果:

客戶端:



服務端:

客戶端中斷後使用netstat -a 命令檢視tcp連線狀態,9000埠處於listen狀態,該連線處於TIME_WAIT狀態



相關文章