系统编程-网络-tcp客户端服务器编程模型(续)、连接断开、获取连接状态场景
相关博文:
系统编程-网络-tcp客户端服务器编程模型、socket、htons、inet_ntop等各API详解、使用telnet测试基本服务器功能
接着该上篇博文,咱们继续,首先,为了内容的完整性和连续性,我们首要的是立马补充、展示客户端的示例代码。
在此之后,之后咱们有两个方向:
一是介绍客户端、服务器编程中一些注意事项,如连接断开、获取连接状态等场景。
一是基于之前的服务器端代码只是基础功能,在支持多客户端访问时将面临困局,进一步,我们需要介绍服务器并发编程模型。
客户端代码
#include <unistd.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<string.h>
#include<errno.h>
#include<stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h> #define PORT 5001
#define SERVER_IP "192.168.1.21" void sig_handler(int signo){
printf("sig_handler=> pid: %d, signo: %d \n", getpid(), signo);
} // 如果使用ctrl+c 终止该进程,服务器也会收到断开连接事件,
// 可见是操作系统底层帮应用程序擦屁股了。 // 直接调用close来关闭该连接,会使得服务器收到断开连接事件。
int main()
{
int sockfd; struct sockaddr_in server_addr;
struct hostent *host; if(signal(SIGPIPE, sig_handler) == SIG_ERR){
//if(signal(SIGPIPE, SIG_DFL) == SIG_ERR){ // SIGPIPE信号的默认执行动作是terminate(终止、退出),所以本进程会退出。
perror("signal error");
} if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
fprintf(stderr, "Socket Error is %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); if (connect(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1)
{
fprintf(stderr, "Connect failed\n");
exit(EXIT_FAILURE);
} char sendbuf[1024];
char recvbuf[2014]; while (1)
{
fgets(sendbuf, sizeof(sendbuf), stdin);
printf("strlen(sendbuf) = %d \n", strlen(sendbuf)); if (strcmp(sendbuf, "exit\n") == 0){
printf("while(1) -> exit \n");
break;
} send(sockfd, sendbuf, strlen(sendbuf), 0); //recv(sockfd, recvbuf, sizeof(recvbuf), 0);
//fputs(recvbuf, stdout); memset(sendbuf, 0, sizeof(sendbuf));
//memset(recvbuf, 0, sizeof(recvbuf));
} close(sockfd);
printf(" client process end \n"); return 0;
}
服务器代码
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> #include <stdint.h> #include <string.h>
#include "server.h"
#include <assert.h> #include <sys/types.h>
#include <unistd.h>
#include <signal.h> // 在Linux网络编程这块,,胡乱包含过多头文件会导致编译不过。
//#include <linux/tcp.h> // 包含下方这个头文件,就不能包含该头文件,否则编译报错。
#include <netinet/tcp.h> // setsockopt函数需要包含此头文件 int server_local_fd, new_client_fd; void sig_deal(int signum){ close(new_client_fd);
close(server_local_fd);
exit(1);
} int main(void)
{
struct sockaddr_in sin; signal(SIGINT, sig_deal); printf("pid = %d \n", getpid()); /*1.创建IPV4的TCP套接字 */
server_local_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_local_fd < 0) {
perror("socket error!");
exit(1);
} /* 2.绑定在服务器的IP地址和端口号上*/
/* 2.1 填充struct sockaddr_in结构体*/
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT); #if 0
// 方式一
sin.sin_addr.s_addr = inet_addr(SERV_IPADDR);
#endif #if 0
// 方式二:
sin.sin_addr.s_addr = INADDR_ANY;
#endif #if 1
// 方式三: inet_pton函数来填充此sin.sin_addr.s_addr成员
if(inet_pton(AF_INET, "192.168.1.21", &sin.sin_addr.s_addr) >0 ){
char buf[16] = {0};
printf("s_addr=%s \n", inet_ntop(AF_INET, &sin.sin_addr.s_addr, buf, sizeof(buf)));
printf("buf = %s \n", buf);
}
#endif /* 2.2 绑定*/
if(bind(server_local_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("bind");
exit(1);
} /*3.listen */
listen(server_local_fd, 5); printf("client listen 5. \n"); char sned_buf[] = "hello, i am server \n"; struct sockaddr_in clientaddr;
socklen_t clientaddrlen; /*4. accept阻塞等待客户端连接请求 */
#if 0
/*****不关心连接上来的客户端的信息*****/ if( (new_client_fd = accept(server_local_fd, NULL, NULL)) < 0) { }else{
/*5.和客户端进行信息的交互(读、写) */
ssize_t write_done = write(new_client_fd, sned_buf, sizeof(sned_buf));
printf("write %ld bytes done \n", write_done); }
#else
/****获取连接上来的客户端的信息******/ memset(&clientaddr, 0, sizeof(clientaddr));
memset(&clientaddrlen, 0, sizeof(clientaddrlen)); clientaddrlen = sizeof(clientaddr);
/***
* 由于cliaddr_len是一个传入传出参数(value-result argument),
* 传入的是调用者提供的缓冲区的长度以避免缓冲区溢出问题,
* 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区).
* 所以,每次调用accept()之前应该重新赋初值。
* ******/
if( (new_client_fd = accept(server_local_fd, (struct sockaddr*)&clientaddr, &clientaddrlen)) < 0) {
perror("accept");
exit(1);
} printf("client connected! print the client info .... \n");
int port = ntohs(clientaddr.sin_port);
char ip[16] = {0};
inet_ntop(AF_INET, &(clientaddr.sin_addr.s_addr), ip, sizeof(ip));
printf("client: ip=%s, port=%d \n", ip, port);
#endif char client_buf[100]={0}; #if 1 // case 1:base function
while(1){
printf("server goes to read... \n");
int bytes_read_done = read(new_client_fd, client_buf, sizeof(client_buf));
printf("bytes_read_done = %d \n", bytes_read_done);
usleep(500000);
}
printf("server process end... \n"); close(new_client_fd);
close(server_local_fd);
#endif #if 0 // case 2 : 当服务器close一个连接时,若client端接着发数据。系统会发出一个SIGPIPE信号给客户端进程,告知这个连接已经断开了,不要再写了。
// SIGPIPE信号的默认执行动作是terminate(终止、退出),所以client会退出。若不想客户端退出可以把SIGPIPE设为SIG_IGN // 在linux下写socket的程序的时候,如果尝试send到一个disconnected socket上,就会让底层抛出一个SIGPIPE信号。
// 验证方法,服务器这里收到一次客户端消息后,就关闭该客户端的描述符。然后客户端内继续向此socket发送数据,观察客户端内代码的运行效果。
while(1){
printf("server goes to read... \n");
int bytes_read_done = read(new_client_fd, client_buf, sizeof(client_buf));
printf("bytes_read_done = %d \n", bytes_read_done); close(new_client_fd);
while(1);
} printf("server process end... \n");
close(server_local_fd);
#endif #if 0 //case 3 : read()返回值小于等于0时,socket连接有可能断开。此时,需要进一步判断errno是否等于EINTR,
// 如果errno == EINTR,则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的,不应close掉该socket连接。
// 如果errno != EINTR,则说明客户端已断开连接,则服务器端可以close掉该socket连接。 if(signal(SIGPIPE, SIG_DFL) == SIG_ERR){
perror("signal error");
} char sendbuf[1024] = "hello i am server\n"; while(1){
printf("server goes to read... \n");
int bytes_read_done = read(new_client_fd, client_buf, sizeof(client_buf));
printf("bytes_read_done = %d \n", bytes_read_done);
if(bytes_read_done <= 0){
if(errno == EINTR){
/*** 对于EINTR的解释 见下方备注 */
printf("network may be ok \n");
}
else
{
printf("network is not alive \n");
}
} int bytes = read(new_client_fd, client_buf, sizeof(client_buf));
printf("==> bytes = %d \n", bytes);
if(bytes <= 0){
if(errno == EINTR){
printf("network may be ok ...\n");
}
else
{
printf("network is not alive ...\n");
}
} // 实测,在客户端已经断开连接的情况下,该send函数仍然返回了 strlen(sendbuf)的有效长度。所以,我们不必寄希望于单纯通过send来获取客户端连接状态信息。
int bytes_send_done = send(new_client_fd, sendbuf, strlen(sendbuf), 0);
printf("bytes_send_done = %d \n", bytes_send_done); while(1){
printf("server is IDLE ... \n");
usleep(500000);
}
} close(new_client_fd);
close(server_local_fd); /*** 对于EINTR的解释
* 一些IO系统调用执行时,如 read 等待输入期间,如果收到一个信号,系统将中断read, 转而执行信号处理函数.
* 当信号处理返回后, 系统遇到了一个问题: 是重新开始这个系统调用, 还是让系统调用失败?
* 早期UNIX系统的做法是, 中断系统调用,并让系统调用失败, 比如read返回 -1, 同时设置 errno 为EINTR.
* 中断了的系统调用是没有完成的调用,它的失败是临时性的,如果再次调用则可能成功,这并不是真正的失败.
* 所以要对这种情况进行处理,
***/
#endif #if 0 //case 4: 使用 getsockopt 实时判断客户端连接状态 实时性高 while(1){ sleep(10); // 你可以在这10秒内进行操作,让客户端进程退出,或者让其保持正常连接 struct tcp_info info;
int len = sizeof(info);
getsockopt(new_client_fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); if((info.tcpi_state == TCP_ESTABLISHED)){
printf("client is connected !\n"); }else{
printf("client is disconnected !\n");
} while(1){
printf("server is IDLE ... \n");
usleep(500000);
}
} close(new_client_fd);
close(server_local_fd); #endif return 0;
}
PS:代码中的备注比较重要,请详细参考。
服务器代码内使用条件编译,共有4个case. 思路如下。
case 1, 基本服务器功能,客户端发数据,服务器收数据代码展示。
case 2 、3、4 都是连接断开时的一些情况
case 2 展示了服务器主动关闭socket连接,对客户端的影响。
case 2, 服务器在收到客户端的一包数据后,就关闭该连接。如果客户端继续向此连接发数据,那么将导致客户端收到13号信号,即SIGPIPE,该信号的默认操作是使进程退出。
case 3、4 展示了客户端断开连接(在客户端中断内敲入exit,即可使得客户端进程退出)后,服务器端如何判断该连接是否已断开的方法。
case 3, read()返回值小于等于0时,socket连接有可能断开。此时,需要进一步判断errno是否等于EINTR。
如果errno == EINTR,则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的,不应close掉该socket连接。
如果errno != EINTR,则说明客户端已断开连接,则服务器端可以close掉该socket连接。
case 4,使用 getsockopt 判断客户端连接状态, 这种方法实时性高, 推荐使用。
相关知识点:
1. 对于EINTR的解释
一些IO系统调用执行时,如 read 等待输入期间,如果收到一个信号,系统将中断read, 转而执行信号处理函数.
当信号处理返回后, 系统遇到了一个问题: 是重新开始这个系统调用, 还是让系统调用失败?
早期UNIX系统的做法是, 中断系统调用,并让系统调用失败, 比如read返回 -1, 同时设置 errno 为EINTR.
中断了的系统调用是没有完成的调用,它的失败是临时性的,如果再次调用则可能成功,这并不是真正的失败.
所以要对这种情况进行处理。
2.
在Linux网络编程这块,胡乱包含过多头文件会导致编译不过。
//#include <linux/tcp.h> // 包含下方这个头文件,就不能包含该头文件,否则编译报错。
#include <netinet/tcp.h> // 使用getsockopt、setsockopt函数,需要包含此头文件。
.
系统编程-网络-tcp客户端服务器编程模型(续)、连接断开、获取连接状态场景的更多相关文章
- --系统编程-网络-tcp客户端服务器编程模型、socket、htons、inet_ntop等各API详解、使用telnet测试基本服务器功能
PART1 基础知识 1. 字节序 网络字节序是大端字节序(低地址存放更高位的字节), 所以,对于字节序为小端的机器需要收发网络数据的场景,要对这些数据进行字节序转换. 字节序转换函数,常用的有四个: ...
- 《UNIX网络编程》TCP客户端服务器例子
最近在看<UNIX网络编程>(简称unp)和<Linux程序设计>,对于unp中第一个获取服务器时间的例子,实践起来总是有点头痛的,因为作者将声明全部包含在了unp.h里,导致 ...
- TCP客户端服务器编程模型
1.客户端调用序列 客户端编程序列如下: 调用socket函数创建套接字 调用connect连接服务器端 调用I/O函数(read/write)与服务器端通讯 调用close关闭套接字 2.服务器端调 ...
- 《UNIX网络编程》TCP客户端服务器:并发、消息回显
经过小小改动,把前面基础的例子做出一点修改. 并发服务器,服务器每accept一个请求就fork()一个新的子进程. 编译运行方法同前一篇. /*client_tcp.c*/ #include < ...
- Linux 下 简单客户端服务器通讯模型(TCP)
原文:Linux 下 简单客户端服务器通讯模型(TCP) 服务器端:server.c #include<stdio.h> #include<stdlib.h> #include ...
- python网络编程socketserver模块(实现TCP客户端/服务器)
摘录python核心编程 socketserver(python3.x版本重新命名)是标准库中的网络编程的高级模块.通过将创建网络客户端和服务器所必须的代码封装起来,简化了模板,为你提供了各种各样的类 ...
- 再次回首 TCP Socket服务器编程
转载:http://www.cnblogs.com/zc22/archive/2010/06/27/1766007.html ------------------ 前言 --------------- ...
- 经过一年时间的沉淀 再次回首 TCP Socket服务器编程--转
------------------ 前言 ------------------ 开发了这么多年,发现最困难的程序开发就是通讯系统. 其他大部分系统,例如CRM/CMS/权限框架/MIS之类的,无论怎 ...
- 【网络编程1】网络编程基础-TCP、UDP编程
网络基础知识 网络模型知识 OSI七层模型:(Open Systems Interconnection Reference Model)开放式通信系统互联参考模型,是国际标准化组织(ISO)提出的一个 ...
随机推荐
- sketch 导出 svg
sketch 导出 svg refs xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许注册用户才可以访问!
- 未来,Baccarat将如何拓展生态版图?
Baccarat最近几度冲上DeFi版面的热搜,一是因为BGV价格不断的上涨,二是因为生态建设者的不断涌入.可以说,当下的Baccarat,实在是太火爆了.那么在未来,Baccarat还将会持续火爆吗 ...
- NDB程序进近复飞保护区的绘制
终于有点空闲,找张图来演练一下<风螺旋标准模板>软件的用法. 某机场NDB进近程序剖面图如下图所示: 该机场采用了近台和远台的双台布局,近台和远台均为NDB与指点标的合装台,没有中间进近定 ...
- .NET探索模型路由约定实现伪静态
概述 IPageRouteModelConvention接口用于自定义PageRouteModel,这个对象在Microsoft.AspNetCore.Mvc.ApplicationModels命名空 ...
- [Python学习笔记]爬虫
要使用Python 抓取网页,首先我们要学习下面四个模块: 包 作用 webbrowser 打开浏览器获取指定页面: requests 从因特网下载文件和网页: Beautiful Soup 解析HT ...
- Tawk.to一键给自己的网站增加在线客服功能
Tawk.to一键给自己的网站增加在线客服功能 很多外贸网站只有contact页面,留下邮箱.电话等联系方式,而在国际贸易当中能够及时在线交流沟通,能给客户留下更好的印象.接下来,就让我们一起来了解一 ...
- 微信小程序(三)-事件绑定
小程序事件绑定 https://developers.weixin.qq.com/miniprogram/dev/framework/view/two-way-bindings.html 1.数据 / ...
- cocos2dx创建工程
p.p1 { margin: 0; font: 17px "Helvetica Neue"; color: rgba(69, 69, 69, 1) } 官网镇楼: http://w ...
- docker封装vue项目并使用jenkins发布
一.概述 vue项目可以打一个dist静态资源包,直接使用Nginx发布即可. 现在由于要上docker,需要将vue项目和nginx打成一个镜像才行. 项目结构如下: ./ ├── build │ ...
- 解决vue 绑定事件会覆盖默认参数的问题
解决vue 绑定事件会覆盖默认参数的问题 在使用一些ui框架的时候,某些组件的框架中的事件所规定的参数不能满足实际开发的需要,但是直接传入参数会把默认的参数覆盖掉 解决方法:将参数放入箭头函数中,传递 ...