相关博文:

系统编程-网络-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客户端服务器编程模型(续)、连接断开、获取连接状态场景的更多相关文章

  1. --系统编程-网络-tcp客户端服务器编程模型、socket、htons、inet_ntop等各API详解、使用telnet测试基本服务器功能

    PART1 基础知识 1. 字节序 网络字节序是大端字节序(低地址存放更高位的字节), 所以,对于字节序为小端的机器需要收发网络数据的场景,要对这些数据进行字节序转换. 字节序转换函数,常用的有四个: ...

  2. 《UNIX网络编程》TCP客户端服务器例子

    最近在看<UNIX网络编程>(简称unp)和<Linux程序设计>,对于unp中第一个获取服务器时间的例子,实践起来总是有点头痛的,因为作者将声明全部包含在了unp.h里,导致 ...

  3. TCP客户端服务器编程模型

    1.客户端调用序列 客户端编程序列如下: 调用socket函数创建套接字 调用connect连接服务器端 调用I/O函数(read/write)与服务器端通讯 调用close关闭套接字 2.服务器端调 ...

  4. 《UNIX网络编程》TCP客户端服务器:并发、消息回显

    经过小小改动,把前面基础的例子做出一点修改. 并发服务器,服务器每accept一个请求就fork()一个新的子进程. 编译运行方法同前一篇. /*client_tcp.c*/ #include < ...

  5. Linux 下 简单客户端服务器通讯模型(TCP)

    原文:Linux 下 简单客户端服务器通讯模型(TCP) 服务器端:server.c #include<stdio.h> #include<stdlib.h> #include ...

  6. python网络编程socketserver模块(实现TCP客户端/服务器)

    摘录python核心编程 socketserver(python3.x版本重新命名)是标准库中的网络编程的高级模块.通过将创建网络客户端和服务器所必须的代码封装起来,简化了模板,为你提供了各种各样的类 ...

  7. 再次回首 TCP Socket服务器编程

    转载:http://www.cnblogs.com/zc22/archive/2010/06/27/1766007.html ------------------ 前言 --------------- ...

  8. 经过一年时间的沉淀 再次回首 TCP Socket服务器编程--转

    ------------------ 前言 ------------------ 开发了这么多年,发现最困难的程序开发就是通讯系统. 其他大部分系统,例如CRM/CMS/权限框架/MIS之类的,无论怎 ...

  9. 【网络编程1】网络编程基础-TCP、UDP编程

    网络基础知识 网络模型知识 OSI七层模型:(Open Systems Interconnection Reference Model)开放式通信系统互联参考模型,是国际标准化组织(ISO)提出的一个 ...

随机推荐

  1. 线上 S1 故障是什么, 线上 S1 故障, 运维故障分级, 运维, 故障分级, P1 级别故障, 故障, P1 , S1

    线上 S1 故障是什么 线上 S1 故障, 运维故障分级, 运维, 故障分级, P1 级别故障, 故障, P1 , S1 故障复盘 https://time.geekbang.org/column/a ...

  2. 关于PCA主成分分析的一点理解

    PCA 即主成分分析技术,旨在利用降维的思想,把多指标转化为少数几个综合指标. 假设目前我们的数据特征为3,即数据维度为三,现在我们想将数据降维为二维,一维: 我们之前的数据其实就是三维空间中的一个个 ...

  3. hadoop环境搭建:高可用

    目录 1.硬件配置 2.软件版本 3.准备工作 3.1.配置网络环境 3.2.安装JDK 3.3.安装ZOOKEEPER 4.安装Hadoop 5.启动 6.问题 7.配置文件 1.硬件配置 采用3台 ...

  4. 自关联映射:一个表自己关联自己,此时从同一个表中查询,通过起别名将一张表变成两张表,使用join语句。

    实例1:id自关联. 隐式内连接: 实例二:编写一个 SQL 查询,来查找与之前(昨天的)日期相比温度更高的所有日期的 id .返回结果 不要求顺序 . 查询结果格式如下例: Weather +--- ...

  5. 生成pdf phantomjs

    注:原创文件,转载请注明出处 使用phantomjs生成还原度比较高的pdf文件,理论上生成word也可以,因需求没有做这块要求,功课留给大家去做了. 下载 https://phantomjs.org ...

  6. Spring IoC总结

    Spring 复习 1.Spring IoC 1.1 基本概念 1.1.1 DIP(Dependency Inversion Principle) 字面意思依赖反转原则,即调用某个类的构造器创建对象时 ...

  7. 后端程序员之路 57、go json

    go自带json处理库,位于encoding/json,里面的test很具参考意义,特别是example_test.go json - The Go Programming Languagehttps ...

  8. 《C++ Primer》笔记 第9章 顺序容器

    顺序容器类型 类型 解释 vector 可变大小数组.支持快速随机访问.在尾部之外的位置插入或删除元素可能很慢 deque 双端队列.支持快速随机访问.在头尾位置插入.删除速度很快 list 双向链表 ...

  9. 使用 Java 开发 Gradle 插件

    Gradle 插件代码可以在 build.gradle 中,buildSrc 项目中,以及独立的插件项目中编写.本文将介绍如何在一个独立的项目中使用 Java 语言编写 Gradle 插件,并发布到仓 ...

  10. selenium之元素定位的方法(一)

    WebDriver 对象有多种方法用于在页面中寻找元素.他们被分成find_element_*和find_elements_*方法.find_element_*方法返回一个WebElement对象,代 ...