tcp ipv4 客户端 服务端 程序简例-尊龙官方平台

tcp ipv4 客户端 服务端 程序简例

el/2024/3/25 16:55:15

tcp ipv4 客户端 & 服务端 程序简例

本文给出基于ipv4的tcp客户端以及服务端程序样例。

服务端:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #define backlog 16int listen_fd = -1;void sigint(int signo);int main()
{if (signal(sigint, sigint) == sig_err){printf("set signal handler(sigint) error!!!\n");exit(1);}// socketif ( (listen_fd = socket(af_inet, sock_stream, 0)) < 0 ){printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}// bindstruct sockaddr_in server_addr;server_addr.sin_family = af_inet; // ipv4server_addr.sin_port = htons(12500); // portserver_addr.sin_addr.s_addr = htonl(inaddr_any); // ipif (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){printf("socket bind error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}// listenif (listen(listen_fd, backlog) < 0){printf("socket listen error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}printf("server init ok, start to accept new connect...\n");while (1){// acceptint client_fd = accept(listen_fd, null, null);if (client_fd < 0){printf("socket accept error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}printf("accept one new connect(%d)!!!\n", client_fd);static const char *msg = "hello, client!\n";if (write(client_fd, msg, strlen(msg)) != strlen(msg)){printf("send msg to client error!!!\n");}close(client_fd);}
}void sigint(int signo)
{printf("catch sigint, quit...\n");close(listen_fd);exit(0);
}

客户端:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include int main()
{int client_fd = socket(af_inet, sock_stream, 0);if (client_fd < 0){printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}struct sockaddr_in server_addr;server_addr.sin_family = af_inet;server_addr.sin_port = htons(12500);if (inet_pton(af_inet, "127.0.0.1", &server_addr.sin_addr) <= 0){printf("inet_pton error!!!\n");exit(1);}if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){printf("socket connect error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}printf("connect to server ok!\n");char msg[1024];int rbytes = -1;while ( (rbytes = read(client_fd, msg, sizeof(msg)-1)) > 0){msg[rbytes] = 0; // null terminateprintf("%s\n", msg);}if (rbytes < 0){printf("read error=%d(%s)!!!\n", errno, strerror(errno));}exit(0);
}

服务端注意事项:

一、server_addr.sin_addr.s_addr = htonl(inaddr_any);

inaddr_any:在服务端套接字listen前,要先进行bind,也就是将一个确定的服务端的“协议地址”与服务端listen套接字绑定。

通常“协议地址”包含三部分:协议族、ip以及port(服务端口)。

其中inaddr_any是用来设置“协议地址”中ip部分。

若协议地址的ip部分设置为inaddr_any,并且服务端所在主机有多个网络接口(多个ip),则服务端可以在任意网络接口(任意ip)上接收客户端链接。

例如服务端所在的主机有三个ip:

10.1.1.1
10.1.1.2
10.1.1.3

如果设置“协议地址”的ip部分为inaddr_any(port=12500),则:
1)有客户端链接10.1.1.1:12500,服务端可以收到syn包并建立链接;
2)有客户端链接10.1.1.2:12500,服务端可以收到syn包并建立链接;
3)有客户端链接10.1.1.3:12500,服务端可以收到syn包并建立链接;

如果设置“协议地址”的ip部分为10.1.1.3(port=12500),则:
1)有客户端链接10.1.1.1:12500,服务端不可以收到syn包并建立链接;
2)有客户端链接10.1.1.2:12500,服务端不可以收到syn包并建立链接;
3)有客户端链接10.1.1.3:12500,服务端可以收到syn包并建立链接;

二、服务端accept到的客户端sockfd都为4

运行服务端程序并且运行三次客户端程序,可以发现服务端有以下输出:

[jiang@localhost 0406]$ ./server 
server init ok, start to accept new connection...
accept one new connect(4)!!!
accept one new connect(4)!!!
accept one new connect(4)!!!
^ccatch sigint, quit...

为啥每次accept返回的客户端的sockfd都相同呢?

首先服务端启动时,fd已经有三个(0=标准输入;1=标准输出;2=标准错误),然后通过socket接口创建了listen_fd(3)。

然后通过accept,从已完成三次握手的队列中取出一个新的客户端链接,创建一个fd,此fd即为4。

服务端每次的行为都一样,先accept生成fd(4),然后写入数据到fd=4中,然后close(fd=4)。

在close后,当前服务端进程中的fd只剩下4个(0、1、2、3),fd=4又变为可用项。

当服务端再次accept生成新的fd时,内核总是从最低可用的fd开始创建占用,由于当前服务端进程有四个fd有各自用途,自然从fd=4重新开始。

每次都是close(fd=4),释放fd=4的状态为可用fd,然后accept时创建fd=4,指代新的客户端socket,往复循环。

客户端注意事项:

三、客户端read时可否不用循环读取?

在客户端connect链接服务端成功后,开始从中read数据,直到read不大于0。

read返回值:

1)>0:读到返回值大小的字节数据;
2)=0:对端关闭连接;
3)<0:read出现错误;

由于tcp是一种“数据流”,服务端写数据,客户端读数据,那客户端怎么知道啥时候“服务端要发送的全发送完毕啦!”这个信息呢?

我们在这里约定,当服务端将要发送的数据全部发送完毕时,就close客户端,通知客户端:我(服务端)要发送给你(客户端)的数据已经发送完毕啦!客户端read到0,得到了这个信息,于是乎就不再(能)继续读。当然,可以由客户端不断从收到的数据中检查某些约定好的“协议控制字符(组)”(这里的协议控制字符可以是任何字符,并非传统意义上的控制字符),例如,约定读取到两个\n就算做一个记录已经被读取完毕,开始下一个记录…即:人为约定、界定一条有效数据(或称为一条记录)的开始和结束。

更何况,万一在read时有信号来了,打断了read调用,导致只从内核接收缓冲区中读取了部分字节到用户态,仅仅调用一次read函数,不管缓冲区中是否有剩余数据,岂不是会导致数据丢失?

其实到这里已经有点偏离主题了…本意是给出一份客户端、服务端关于socket api的使用样例,但是…

我上面的程序距离真正可用、能用、实用还有很多路要走,存在有很多可优化的地方,比如“so_reuseaddr ”等…


http://www.ngui.cc/el/5127111.html

相关文章

tcp ipv6 客户端 服务端 程序简例

tcpipv6 客户端 & 服务端 程序简例 本文给出基于ipv6的tcp客户端以及服务端程序样例。 可以参考《tcpipv4 客户端 & 服务端 程序简例》进行对比。 服务端: #include #include #include #include <…

c:判断主机字节序(大端be小端le)

c:判断主机字节序(大端be小端le) 通过简单的c程序可以判断当前主机字节序是大端(big-endian:be)或小端(little endian:le)。 code: #include

c:位运算之 左移运算和右移运算

c:位运算之 左移运算(<<)和右移运算(>>) 在c中,位运算包含两种移位运算: 左移运算:<< 右移运算:>> 左右位移运算,在数值为无符号…

linux:vim最多一次复制50行解决办法

linux:vim最多一次复制50行解决办法 linux默认情况下,vim一次最多可以复制50行数据(从一个文件到另一个文件)。 如何解决? 修改$home/.vimrc(如果不存在则新建),追加:…

linux:vim个性化配置

linux:vim个性化配置 $home/.vimrc文件用于配置当前用户vim个性化设置。 我的.vimrc文件: syn on set nu set hlsearch set tabstop4 set laststatus2 set viminfo1000 set cursorline set mouse "set paste如果~/.vimrc不存在,新建即…

linux:sudo选择特定用户身份执行某命令(集)

linux:sudo选择特定用户身份执行某命令(集) sudo switch user do sudo allows a permitted user to execute a command as the superuser or another user, as specified by the security policy. 有时我们有这样的需求: 让一…

c/c :文件描述符与进程之间的关系

c/c:文件描述符与进程之间的关系 在unix中,文件在进程中通常抽象化为文件描述符(file descriptor)。 文件描述符是一个非负整数,可以理解为一个句柄。 我们可以通过open一个磁盘文件,获取一个文件描述符…

c/c :tcp服务压测客户端connect报错(cannot assign requested address)

c/c:tcp服务压测客户端connect报错(cannot assign requested address) 最近我们对自己的服务器进行了一次压测,在测试中出现客户端在调用connect时报错: cannot assign requested address(errno99&#x…

c/c :进程资源限制函数(getrlimit、setrlimit) shell-ulimit命令

c/c:进程资源限制函数(getrlimit、setrlimit)&& shell-ulimit命令 进程在操作系统内核中是一个独立存在的运行实体。 每个进程都有 一组 资源限制,限制进程对于系统资源的申请量。 可以通过 getrlimit 来获取当前进程某…

c/c :tcp的三次握手和四次挥手(实验)

c/c:tcp的三次握手和四次挥手(实验) 建立tcp连接需经过三次握手,释放tcp连接需经过四次挥手。 tcp报文段的首部中,比较重要的字段除了源端口、目的端口,还有以下几个字段与tcp连接的建立与释放相关&#…
网站地图