【TCP服务器的演变过程】编写第一个TCP服务器:实现一对一的连接通信

一、前言

手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒

为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。
在这里插入图片描述

二、需要使用到的API

2.1、socket()函数

函数原型:

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);

这个函数建立一个协议族、协议类型、协议编号的socket文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1并设置了errno。

domain参数值含义:

名称 含义
PF_UNIX,PF_LOCAL 本地通信
AF_INET,PF_INET IPv4协议
PF_INET6 IPv6协议
PF_NETLINK 内核用户界面设备
PF_PACKET 底层包访问

type参数值含义:

名称 含义
SOCK_STREAM TCP连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输
SOCK_DGRAM UDP连接
SOCK_SEQPACKET 序列化包,提供一个序列化的、可靠的、双向的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出
:SOCK_PACKET 专用类型
SOCK_RDM 提供可靠的数据报文,不保证数据有序
SOCK_RAW 提供原始网络协议访问

protocol参数含义:
通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;如果协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

2.2、bind()函数

函数原型:

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

参数说明:

  • 第1个参数sockfd是用socket()函数创建的文件描述符。
  • 第2个参数my_addr是指向一个结构为sockaddr参数的指针,sockaddr中包含了地址、端口和IP地址的信息。
  • 第3个参数addrlen是my_addr结构的长度,可以设置成sizeof(struct sockaddr)。

bind()函数的返回值为0时表示绑定成功,-1表示绑定失败并设置了errno。

2.3、listen()函数

函数原型:

#include<sys/socket.h>
int listen(int sockfd, int backlog);

参数说明:

  • 第1个参数sockfd是用socket()函数创建的文件描述符。
  • 第2个参数backlog规定了内核应该为相应套接字排队的最大连接个数。

2.4、accept()函数

函数原型:

#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

参数说明:

  • sockefd:套接字描述符,该套接字在listen() 后监听连接。
  • addr:(可选)指针。指向一个缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
  • addrlen:(可选)指针。输入参数,配合addr一起使用,指向存有addr地址长度的整形数。

2.5、recv()函数

函数原型:

#include<sys/types.h>
#include<sys/socket.h>
int recv( int fd, char *buf, int len, int flags);

参数说明:

  • 第一个参数指定接收端套接字描述符;
  • 第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
  • 第三个参数指明buf的长度;
  • 第四个参数一般置0。

返回值:

  • 返回大于0的数,表示介绍到的数据大小。
  • 返回0,表示连接断开。
  • 返回-1,表示接受数据错误。

2.6、send()函数

函数原型:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数说明:

  • sockfd:向套接字中发送数据
  • buf:要发送的数据的首地址
  • len:要发送的数据的字节
  • int flags:设置为MSG_DONTWAITMSG 时 表示非阻塞,设置为0时 功能和write一样。

返回值:成功返回实际发送的字节数,失败返回 -1并设置了errno。

2.7、strerror()函数

strerror()函数返回一个指向字符串的指针,该字符串描述参数errnum中传递的错误代码,可能使用当前语言环境的LC_MESSAGES部分来选择适当的语言。(例如,如果errnum为EINVAL,则返回的描述将为“无效参数”。)应用程序不能修改此字符串,但可以通过随后调用strerror()strerror_l()来修改。任何其他库函数,包括perror(),都不会修改此字符串。

函数原型:

#include <string.h>

char *strerror(int errnum);

int strerror_r(int errnum, char *buf, size_t buflen);
/* XSI-compliant */
char *strerror_r(int errnum, char *buf, size_t buflen);
/* GNU-specific */
char *strerror_l(int errnum, locale_t locale);

三、实现步骤

一对一服务器设计:
在这里插入图片描述

(1)创建socket。

int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
    
    
    printf("errno = %d, %s\n",errno,strerror(errno));
    return SOCKET_CREATE_FAILED;
}

(2)绑定地址。

struct sockaddr_in server;
memset(&server,0,sizeof(server));

server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);

if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
    
    
    printf("errno = %d, %s\n",errno,strerror(errno));
    close(listenfd);
    return SOCKET_BIND_FAILED;
}

(3)设置监听。

if(-1==listen(listenfd,BLOCK_SIZE)){
    
    
   printf("errno = %d, %s\n",errno,strerror(errno));
   close(listenfd);
   return SOCKET_LISTEN_FAILED;
}

(4)接收连接。

struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);
    
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
    
    
    printf("errno = %d, %s\n",errno,strerror(errno));
    close(listenfd);
    return SOCKET_ACCEPT_FAILED;
}

(5)接收数据。

char buf[BUFFER_LENGTH]={
    
    0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {
    
    
     printf("connection dropped\n");

}
printf("recv --> %s\n",buf);

(6)发送数据。

if(-1==send(clientfd,buf,ret,0))
{
    
    
    printf("errno = %d, %s\n",errno,strerror(errno));
}

(7)关闭文件描述符。

close(clientfd);
close(listenfd);

四、完整代码

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

#include <errno.h>
#include <string.h>
#include <unistd.h>


#define LISTEN_PORT     8888
#define BLOCK_SIZE      10
#define BUFFER_LENGTH   1024

enum ERROR_CODE{
    
    
    SOCKET_CREATE_FAILED=-1,
    SOCKET_BIND_FAILED=-2,
    SOCKET_LISTEN_FAILED=-3,
    SOCKET_ACCEPT_FAILED=-4
};



int main(int argc,char **argv)
{
    
    
    // 1.
    int listenfd=socket(AF_INET,SOCK_STREAM,0);
    if(listenfd==-1){
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        return SOCKET_CREATE_FAILED;
    }

    // 2.
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));

    server.sin_family=AF_INET;
    server.sin_addr.s_addr=htonl(INADDR_ANY);
    server.sin_port=htons(LISTEN_PORT);

    if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_BIND_FAILED;
    }

    // 3.
    if(-1==listen(listenfd,BLOCK_SIZE)){
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_LISTEN_FAILED;
    }

    // 4.
    struct sockaddr_in client;
    memset(&client,0,sizeof(client));
    socklen_t len=sizeof(client);
    
    int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
    if(clientfd==-1){
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_ACCEPT_FAILED;
    }

    printf("client fd = %d\n",clientfd);
    int ret=1;
    while(ret>0){
    
    
        // 5.
        char buf[BUFFER_LENGTH]={
    
    0};
        ret=recv(clientfd,buf,BUFFER_LENGTH,0);
        if(ret==0) {
    
    
            printf("connection dropped\n");
            break;

        }
        
        printf("recv --> %s\n",buf);
        if(-1==send(clientfd,buf,ret,0))
        {
    
    
            printf("errno = %d, %s\n",errno,strerror(errno));
        }
        
    }
    close(clientfd);
    close(listenfd);

    return 0;
}

编译命令:

gcc -o server server.c

五、TCP客户端

5.1、自己实现一个TCP客户端

自己实现一个TCP客户端连接TCP服务器的代码:

#include <stdio.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>

#include <unistd.h>
#include <stdlib.h>

#define BUFFER_LENGTH   1024

enum ERROR_CODE{
    
    
    SOCKET_CREATE_FAILED=-1,
    SOCKET_CONN_FAILED=-2,
    SOCKET_LISTEN_FAILED=-3,
    SOCKET_ACCEPT_FAILED=-4
};

int main(int argc,char** argv)
{
    
    
    if(argc<3)
    {
    
    
        printf("Please enter the server IP and port.");
        return 0;
    }
    printf("connect to %s, port=%s\n",argv[1],argv[2]);

    int connfd=socket(AF_INET,SOCK_STREAM,0);
    if(connfd==-1)
    {
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        return SOCKET_CREATE_FAILED;

    }
    struct sockaddr_in serv;
    serv.sin_family=AF_INET;
    serv.sin_addr.s_addr=inet_addr(argv[1]);
    serv.sin_port=htons(atoi(argv[2]));
    socklen_t len=sizeof(serv);
    int rwfd=connect(connfd,(struct sockaddr*)&serv,len);
    if(rwfd==-1)
    {
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(rwfd);
        return SOCKET_CONN_FAILED;
    }
    int ret=1;
    while(ret>0)
    {
    
    
        char buf[BUFFER_LENGTH]={
    
    0};
        printf("Please enter the string to send:\n");
        scanf("%s",buf);
        send(connfd,buf,strlen(buf),0);

        memset(buf,0,BUFFER_LENGTH);
        printf("recv:\n");
        ret=recv(connfd,buf,BUFFER_LENGTH,0);
        printf("%s\n",buf);
        
    }
    close(rwfd);
    return 0;
}

编译:

gcc -o client client.c

5.2、Windows下可以使用NetAssist的网络助手工具

在这里插入图片描述

下载地址:http://old.tpyboard.com/downloads/NetAssist.exe

小结

至此,我们实现了一个一对一的服务器连接,这阶段的TCP服务器代码只能接收一个客户端接入,如果客户端断开了就会直接退出。重点是掌握开发TCP服务器的基本流程,下一章节将介绍在此基础上进行升级,实现可以接受多个客户端的同时接入。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Long_xu/article/details/135118661