编写tcp_server

本文详细介绍了TCP服务器的创建过程,包括TCP/IP协议中的字节序转换、socket接口使用、服务器端的bind、listen、accept和客户端的connect操作。通过示例代码展示了单连接服务器,并讨论了多进程和多线程版本的实现。此外,还分析了TCP TIME_WAIT状态对服务器重启的影响及其解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景知识:

1.在TCP/IP协议中,IP地址+TCP/UCP端口号 —-》唯一标识网络通讯中的一个进程,
并且IP地址+端口号被称为socket。

2.内存中的多字节数据相对于内存地址有大端和小端之分,
而网络数据流同样有大端和小端之分。

TCP/IP协议规定,网络数据流应采用大端字节序,即就是低地址高字节。

所以:发送端把数据发送到发送缓冲区之前需要做字节序的转换。
而接收主机在接收时也需要将接收缓冲区中的数据做字节序的转换。

将网络字节序和主机字节序相互转换的函数:

这里写图片描述

3.socket地址的数据类型及相关函数:
socket API是一层抽象的网络编程接口,使用于各种底层网络协议(IPV4,IPV6)
也就是该接口使得任何的网络协议都可以使用同一个接口,而参数不匹配等问题都是该函数要做的工作。

sockaddr的数据结构:

这里写图片描述

因为要使用统一的接口,所以在传参时需要注意将所有类型的结构体指针都强转为(struct sockaddr*)。

可能有朋友觉得为什么不用void*呢?这样还会使得接口更加的简单,而且使每次的传参过程简单好多,不需要了解结构体的类型,直接传就可以,因为void*可以接收任意类型的指针。

其实也很简单,因为sock API的实现早于ANSI C标准化,那时还没有void*类型,y8inwie,这些函数的参数用struct sockaddr*类型表示,在传递参数之前要强制类型转换一下。


4.对于IP地址,存放在结构体sockaddr_in中的成员struct in_addr sin_addr
表示32位的IP地址。
而我们通常习惯性的用点分十进制的字符串表示IP地址。
下面函数就可以完成我们的需求—-》将点分十进制的IP字符串转换为整形。

1)字符串转in_addr的函数:

这里写图片描述

函数原型:

①创建一个套接字(获得一个文件描述符)
对于server和client来说,都需要创建一个套接字,来标识自己主机的IP地址及端口号。
一对套接字代表一个连接。

这里写图片描述

type:SOCK_STREAM表示tcp协议是面向字节流的传输协议。
SOCK_DGRAM表示udp协议是面向数据报的传输协议。

socket()如果调用成功的话,就返回一个文件描述符,应用程序可以用read/write来读写文件。
如果失败,就返回-1。

②bind(绑定本地主机的IP地址和端口号)

这里写图片描述

服务器程序所监听的网络地址和端口号一般通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接。
因此服务器需要调用调用bind()绑定一个固定的网络地址和端口号。

由于客户端不需要固定的端口号,因此不需要绑定,。
客户端的端口号是由内核自动分配的。

功能:
将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。

返回值:
成功返回0,失败返回-1;

注意:
这里的网络地址可以是INADDR_ANY (0),这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址。

③listen(监听)—-》只有服务器端才会调用的接口函数。
客户端不需要监听,只需要请求连接就好。

这里写图片描述

连接等待状态,如果接收到更多的连接请求就忽略。
返回值:成功返回0,失败返回-1.

④accept–>(接收请求)

这里写图片描述

描述信息:
三方握手完成后(tcp底层所做的工作),服务器调用accept()阻塞式等待接收请求,直到有客户端连接上来。

成功返回时accept函数带回了客户端的IP地址和端口号,因为tcp是全双工的,所以服务器端也可能会发送消息给客户端,那么就需要知道客户端的套接字,即就是客户端的IP地址和端口号。

⑤connect—-》(客户端需要的接口函数)

这里写图片描述

客户端需要调用该函数连接服务器,connect和bind的参数形式一致,区别在于绑定时绑定自己的地址,而连接时需要对方的地址。

返回值:成功返回0,失败返回-1.


version1: 服务器端程序:

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#include<netinet/in.h>

static void Usage(const char* proc)
{
    printf("%s : [server_ip][server_port]\n",proc);
}

int  startup(char* ip,int  port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0)
    {
        perror("sock");
        exit(1);
    }
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);
    socklen_t len = sizeof(local);
    if(bind(sock,(struct sockaddr*)&local,len)< 0)
    {
        perror("bind");
        exit(2);
    }

    if(listen(sock,3) < 0)
    {
        perror("listen");
        exit(3);
    }
    return sock;
}

int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(4);
    }
        //接待客人的人
    int listen_sock = startup(argv[1],atoi(argv[2]));
    printf(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值