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

news/2024/5/18 15:29:30 标签: tcp/ip, 服务器, 网络协议, 网络, linux, udp, 后端

编写第一个TCP服务器:实现一对一的连接通信

  • 一、前言
  • 二、需要使用到的API
    • 2.1、socket()函数
    • 2.2、bind()函数
    • 2.3、listen()函数
    • 2.4、accept()函数
    • 2.5、recv()函数
    • 2.6、send()函数
    • 2.7、strerror()函数
  • 三、实现步骤
  • 四、完整代码
  • 五、TCP客户端
    • 5.1、自己实现一个TCP客户端
    • 5.2、Windows下可以使用NetAssist的网络助手工具
  • 小结

一、前言

手把手教你从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_INETIPv4协议
PF_INET6IPv6协议
PF_NETLINK内核用户界面设备
PF_PACKET底层包访问

type参数值含义:

名称含义
SOCK_STREAMTCP连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输
SOCK_DGRAMUDP连接
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服务器的基本流程,下一章节将介绍在此基础上进行升级,实现可以接受多个客户端的同时接入。
在这里插入图片描述


http://www.niftyadmin.cn/n/5277572.html

相关文章

使用java和react关于EasyExcel导出图片的问题,文件打开提示已损坏

使用java和react关于EasyExcel导出图片的问题&#xff0c;文件打开提示已损坏 这是alibaba开放的一个导出工具&#xff0c;便捷好用&#xff0c;参考官方文档&#xff1a; https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write#%E5%9B%BE%E7%89%87%E5%AF%B…

Python接口自动化测试实战(视频教程+源码)

接口自动化测试是指通过编写程序来模拟用户的行为&#xff0c;对接口进行自动化测试。Python是一种流行的编程语言&#xff0c;它在接口自动化测试中得到了广泛应用。下面详细介绍Python接口自动化测试实战。 1、接口自动化测试框架 在Python接口自动化测试中&#xff0c;我们…

从入门到放弃之「ClickHouse」

文章目录 1. 写在最前面1.1 思路 2. ClickHouse2.1 基本概念2.2 高端用法2.2.1 条件判断2.2.2 HAVING2.2.3 CASE WHEN2.2.4 window function 3. 碎碎念4. 参考资料 1. 写在最前面 最近在整理 api 成功率的问题。但是总结下来以下三点是我分析路上的绊脚石。 上报链路还不够稳定…

【点选验证码识别】某招标网站反爬虫分析与验证码自动识别

文章目录 1. 写在前面2. 风控描述3. 验证码裁剪4. 验证码识别 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向感兴趣…

Java_集合进阶(Collection和List系列)

一、集合概述和分类 1.1 集合的分类 已经学习过了ArrayList集合&#xff0c;但是除了ArrayList集合&#xff0c;Java还提供了很多种其他的集合&#xff0c;如下图所示&#xff1a; 我想你的第一感觉是这些集合好多呀&#xff01;但是&#xff0c;我们学习时会对这些集合进行…

Java基于微信小程序的小区车位租赁系统的设计与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 简介技术路线需求分析用户信息管理车位信息管理车位申请管理论坛信息管理 效果图推荐阅读 简介 …

一文了解提示工程(Prompt Engineering)

引言 在机器学习的世界里&#xff0c;有一句众所周知的话&#xff0c;“机器学习模型的好坏取决于您为其提供的训练数据。” 它指出了数据质量在您从这些算法中获得的结果中发挥的关键作用。 在使用生成式 AI 模型时&#xff0c;这一想法也很重要 - 无论它们生成文本、代码还…

【DP】62.不同路径

题目 法1&#xff1a;二维DP 必须掌握&#xff01; class Solution {public int uniquePaths(int m, int n) {int[][] matrix new int[m][n];Arrays.fill(matrix[0], 1);for (int i 0; i < m; i) {matrix[i][0] 1;}for (int i 1; i < m; i) {for (int j 1; j <…