相关博文:html
接着该上篇博文,我们继续,首先,为了内容的完整性和连续性,咱们首要的是立马补充、展现客户端的示例代码。linux
在此以后,以后我们有两个方向:编程
一是介绍客户端、服务器编程中一些注意事项,如链接断开、获取链接状态等场景。服务器
一是基于以前的服务器端代码只是基础功能,在支持多客户端访问时将面临困局,进一步,咱们须要介绍服务器并发编程模型。网络
客户端代码并发
#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;
}
服务器代码app
#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:代码中的备注比较重要,请详细参考。socket
服务器代码内使用条件编译,共有4个case. 思路以下。tcp
case 1, 基本服务器功能,客户端发数据,服务器收数据代码展现。 函数
case 2 、三、4 都是链接断开时的一些状况
case 2 展现了服务器主动关闭socket链接,对客户端的影响。
case 2, 服务器在收到客户端的一包数据后,就关闭该链接。若是客户端继续向此链接发数据,那么将致使客户端收到13号信号,即SIGPIPE,该信号的默认操做是使进程退出。
case 三、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函数,须要包含此头文件。
.