socket编程 -- 基于TCP协议的C/S通信模型及实现

基于TCP协议的客户端/服务器的程序

下图是基于TCP协议的客户端/服务器程序的一般流程

socket编程 -- 基于TCP协议的C/S通信模型及实现_第1张图片

服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。


数据传输的过程
建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是 由客户端主动发起请求,服务器被动处理请求,一问一答的方式。
因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。
如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样, 服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。

注意:
任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。
如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。


客户端/服务器程序的实例

通过最简单的客户端/服务器程序的实例:

server服务端代码:
server.c的作用是从客户端读字符,然后将每个字符转换为大写并回送给客户端。

/* server.c */
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXLINE 80
#define SERV_PORT 8000

int main(void)
{
    /*网络套接字地址结构体 服务端servaddr 客户端cliaddr*/   
    struct sockaddr_in servaddr, cliaddr; 
    socklen_t cliaddr_len;  
    int listenfd, connfd;   //监听文件描述符、连接文件描述符
    char buf[MAXLINE];      
    char str[INET_ADDRSTRLEN];//#define INET_ADDRSTRLEN 16  /* for IPv4 dotted-decimal */ 
    int i, n;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);  //打开一个网络通讯端口,分配一个文件描述符listenfd

    bzero(&servaddr, sizeof(servaddr));          //清空服务端套接字
    servaddr.sin_family = AF_INET;               //地址采用IPv4地址 
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//地址从主机字节顺序转换成网络字节顺序
    servaddr.sin_port = htons(SERV_PORT);        //端口号从主机字节顺序转换成网络字节顺序

    /*将文件描述符listenfd和服务器地址绑定在一起*/
    bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

    /*声明listenfd处于监听状态,并且最多允许有20个客户端处于待连接状态*/
    listen(listenfd, 20);

    printf("等待连接中...\n");
    while(1){
        cliaddr_len = sizeof(cliaddr);
        /*connfd文件描述符用于和客户端通信,服务器调用accept()接受连接*/
        connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
        /*从connfd中读取数据*/
        n = read(connfd, buf, MAXLINE);
        printf("received from %s at PORT %d\n",
                inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                ntohs(cliaddr.sin_port));

        for(i = 0; itoupper(buf[i]);
        }
        /*往connfd中写数据*/
        write(connfd, buf, n);
        close(connfd);
    }
}

client客户端代码
client.c的作用是从命令行参数中获得一个字符串发给服务器,然后接收服务器返回的 字符串并打印。

/* client.c */
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, n;
    char *str;

    if(argc != 2){
        fputs("args err\n", stderr);
        exit(1);
    }

    str = argv[1];
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);
    /*连接*/
    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    /*向sockfd写入字符串str*/
    write(sockfd, str, strlen(str));
    /*读取经过服务端处理的sockfd到buf中*/
    n = read(sockfd, buf, MAXLINE);
    printf("服务端应答:\n");
    /*输出buf*/
    write(STDOUT_FILENO, buf, n);
    close(sockfd);

    return 0;
}

编译执行及运行结果:

yu@ubuntu:~/Linux/212$ gcc -o client client.c
yu@ubuntu:~/Linux/212$ gcc -o server server.c
yu@ubuntu:~/Linux/212$ ls
client  client.c  server  server.c
yu@ubuntu:~/Linux/212$ ./server
等待连接中...
received from 127.0.0.1 at PORT 41404
received from 127.0.0.1 at PORT 41405
received from 127.0.0.1 at PORT 41406
received from 127.0.0.1 at PORT 41407

另开一终端执行客户端程序:

yu@ubuntu:~/Linux/212$ ./client hey
服务端应答:
HEY
yu@ubuntu:~/Linux/212$ ./client howareyou
服务端应答:
HOWAREYOU
yu@ubuntu:~/Linux/212$ ./client 3q
服务端应答:
3Q
yu@ubuntu:~/Linux/212$ ./client bye
服务端应答:
BYE

你可能感兴趣的:(Linux,network,programming)