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

网络编程-一个简单的客户端与服务器程序(0),(看这篇就够了)

程序员文章站 2022-07-14 20:52:39
...

写客户端与服务器程序之前,相信都对TCP的三次握手,四次挥手有一定的了解。如果还不了解的朋友可以看这两篇网络编程-从TCP三次握手说起网络编程-TCP的四次挥手

服务器和客户端程序的流程

基于TCP/IP协议的服务器和客户端程序的一般流程,如下图所示:

网络编程-一个简单的客户端与服务器程序(0),(看这篇就够了)

1、服务器初始化一一LISTEN

(1)调用socket函数创建文件描述符。
(2)调用bind函数将当前的文件描述符和ip/port绑定在一起。如果这个端口已经被其他进程占用了,就会bind失败。
(3)调用listen函数声明当前这个文件描述符作为一个服务器的文件描述符。为accept做好准备。
(4)调用accept函数阻塞等待客户端连接起来。

2、建立连接的过程一一三次握手

(1) 服务端启动,并监听等待,处于LISTEN状态
(2) Client端首先发送一个SYN包告诉Server端,我的初始***是X,处于SYN_SENT状态。
(3) 服务端收到后,并回应ACK=X+1和seq=Y,处于SYN_RCVD状态,客户端发送能力,服务端接收能力正常。
(4) 客户端收到服务端的ACK,连接建立,同时向服务端回复ACK,处于ESTABLISHED状态。
(5)服务端收到ACK,连接建立,处于ESTABLISHED状态,客户端接收能力正常。

3、数据传输的过程

建立连接后,TCP协议提供全双工的通信服务。所谓的全双工,意思是:在同一条链路中的同一时刻,通信双方可以同时写数据。那什么是半双工呢?就是在同一条链路中的同一时刻,只能由一方来读写数据。

(1)服务器从accept函数返回后立刻调用read函数读socket里数据。读socket就像读管道一样,如果没有数据到达就阻塞等待。
(2)客户端调用write函数发送请求给服务器,服务器收到后就向客户端回复ACK,并从read函数中返回,对客户端的请求进行处理。在此期间客户端调用read函数阻塞等待服务器的应答。
(3)服务器调用wirte函数将处理结果返回客户端,客户端收到后回复后ACK,服务器再次调用read函数等待下一条请求。
(4)客户端从read函数返回,并发送下一条请求。如此循环下去。

4、断开连接的过程一一四次挥手

(1)客户端没有更多的请求调用close函数关闭连接,客户端会向服务器发送FIN端。

(2)服务器收到FIN后会回应ACK,同时read返回0

(3)客户端收到FIN后,在,在返回一个ACK给服务器。

说了这么多,我们先先看看代码的实现:

客户端代码client.c如下:


#include <netdb.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h>
#include <string.h> 
#include <sys/socket.h> 

#define MAX 80 
#define PORT 8080 
#define SA struct sockaddr 

void func(int sockfd) { 
    char buff[MAX]; 
    int n; 
    for (;;) { 

        bzero(buff, sizeof(buff)); 
        printf("Enter the string : "); 
        n = 0; 
        //从控制台读取消息
        while ((buff[n++] = getchar()) != '\n') 
            ; 
        write(sockfd, buff, sizeof(buff)); 
        bzero(buff, sizeof(buff)); 
        read(sockfd, buff, sizeof(buff)); 
        printf("From Server : %s", buff); 

        if ((strncmp(buff, "exit", 4)) == 0) { 
            printf("Client Exit...\n"); 
            break; 
        } 
    } 
} 
  
int main(int argc, char const *argv[]){ 
    int sockfd; //连接符
    struct sockaddr_in servaddr; 
  
    // 套接字创建
    sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if (sockfd == -1) { 
        printf("socket creation failed...\n"); 
        exit(0); 
    } 
    else
        printf("Socket successfully created..\n"); 
    bzero(&servaddr, sizeof(servaddr)); //初始化结构体
  
    //分配IP,端口
    servaddr.sin_family = AF_INET; 
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
    servaddr.sin_port = htons(PORT); 
  
     //连接服务器,如果非0,则连接失败
    if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) { 
        printf("connection with the server failed...\n"); 
        exit(0); 
    } 
    else
        printf("connected to the server..\n"); 
  
    
    func(sockfd); 
  
    // 关闭套接字
    close(sockfd); 
} 

服务端代码server.c如下:


#include <netdb.h> 
#include <netinet/in.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h> 
#include <sys/types.h> 
#define MAX 80 
#define PORT 8080 
#define SA struct sockaddr 
  

void func(int sockfd) { 
    char buff[MAX]; 
    int n; 
    // infinite loop for chat 
    for (;;) { 
        bzero(buff, MAX); 
  
        //读取客户端发送的消息
        read(sockfd, buff, sizeof(buff)); 
        // print buffer which contains the client contents 
        printf("From client: %s\t To client : ", buff); 
        bzero(buff, MAX); 
        n = 0; 
        //将读取内容原封不动地发送回去
        while ((buff[n++] = getchar()) != '\n') 
            ; 
  
        // and send that buffer to client 
        write(sockfd, buff, sizeof(buff)); 
  
        // if msg contains "Exit" then server exit and chat ended. 
        if (strncmp("exit", buff, 4) == 0) { 
            printf("Server Exit...\n"); 
            break; 
        } 
    } 
} 
  
int main(int argc, char const *argv[]){

    int sockfd, connfd, len; 
    struct sockaddr_in servaddr, cli; 
  
    
    sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if (sockfd == -1) { 
        printf("socket creation failed...\n"); 
        exit(0); 
    } 
    else
        printf("Socket successfully created..\n"); 
    //初始化服务端socket信息
    bzero(&servaddr, sizeof(servaddr)); 
  
    //使用默认的ip和port 
    servaddr.sin_family = AF_INET; 
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    servaddr.sin_port = htons(PORT); 
  
    //绑定指定ip和端口
    if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0) { 
        printf("socket bind failed...\n"); 
        exit(0); 
    } 
    else
        printf("Socket successfully binded..\n"); 
  
    // 现在服务器已经准备好监听
    if ((listen(sockfd, 5)) != 0) { 
        printf("Listen failed...\n"); 
        exit(0); 
    } 
    else
        printf("Server listening..\n"); 
    len = sizeof(cli); 
  
     //处理来自客户端的连接
    connfd = accept(sockfd, (SA*)&cli, &len); 
    if (connfd < 0) { 
        printf("server acccept failed...\n"); 
        exit(0); 
    } 
    else
        printf("server acccept the client...\n"); 
  

    func(connfd); 
  
    // 关闭套接字
    close(sockfd); 
} 

编译客户端服务端代码:

gcc -o client client.c
gcc -o server server.c

在两个终端先运行server,在运行client。

运行客户端,并输入内容:

网络编程-一个简单的客户端与服务器程序(0),(看这篇就够了)服务器:

网络编程-一个简单的客户端与服务器程序(0),(看这篇就够了)从运行结果可以看到,客户端连接到服务端后,发送一段字符串“hello程序猿编码”后,服务端返回同样的字符串,达到了我们想要的目的。当然代码里有很多地方还需要完善,但这不影响我们对网络编程的学习。

基于TCP/IP 协议的套接字的工作流程

网络编程-一个简单的客户端与服务器程序(0),(看这篇就够了)

客户端-服务端

在弄清楚图中的接口含义之前,实际上你可以认为客户端连接服务器的整个过程你可以看成是这样的(阻塞模式):

(1) 服务端准备(socket,bind,listen,accept等待客户端)

(2)客户端准备(socket)

(3)客户端连接(connect)

(4)服务端收到客户端的连接(accept返回);客户端连接成功,connect返回

(5)客户端发送数据(write)

(6)服务端接收数据(read),随后又将原数据发回(write)

(7)客户端收到来自服务端的数据(read)

总结

TCP/IP 协议的套接字的工作流程所提到的接口数据结构的介绍和使用说明,都会在后续进行详细介绍。

参考:TCP协议的通讯流程
网络编程-一个简单的客户端与服务器程序(0),(看这篇就够了)
(微信公众号【程序猿编码】)

网络编程-一个简单的客户端与服务器程序(0),(看这篇就够了)
(添加本人微信号,备注加群,进入程序猿编码交流群,领取学习资料,获取每日干货)

微信公众号【程序猿编码】,这里Linux c/c++ 、Go语言、数据结构与算法、网络编程相关知识,常用的程序员工具。还有汇聚精炼每日时政、民生、文化、娱乐新闻简报,即刻知晓天下事!

相关标签: UNIX网络编程