TCP客户-服务器程序例子
首先编写一个客户端在标准输入上读取内一行内容,然后发送给服务器,之后服务器直接返回数据报的内容,此时客户端收到信息,并且把数据报写到标准输出设备上,如下图
此章节就是根据这个小例子,来让你知道某些api底层的东西,使你对这些api的了解更加深刻
贴代码
服务器端的小程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <strings.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define SERV_PORT 9877
#define WAIT_COUNT 5
#define READ_SIZE 257
void send_myself(int fd);
void sig_child(int signo)
{
int stat;
pid_t pid = wait(&stat);
printf("pid is %d\n",pid);
return;
}
int main(int argc, char** argv)
{
int listen_fd, real_fd;
struct sockaddr_in listen_addr, client_addr;
socklen_t len = sizeof(struct sockaddr_in);
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd == -1)
{
perror("socket failed ");
return -1;
}
signal(SIGCHLD, sig_child);
bzero(&listen_addr,sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
listen_addr.sin_port = htons(SERV_PORT);
bind(listen_fd,(struct sockaddr *)&listen_addr, len);
listen(listen_fd, WAIT_COUNT);
while(1)
{
real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);
if(real_fd == -1)
{
perror("accpet fail ");
return -1;
}
if(fork() == 0)
{
close(listen_fd);
send_myself(real_fd);
close(real_fd);
exit(0);
}
close(real_fd);
}
return 0;
}
void send_myself(int fd)
{
char tmp[READ_SIZE] = {0};
while(1)
{
int size = read(fd ,tmp,READ_SIZE-1);
write(STDOUT_FILENO ,tmp,size);
if(size == 0)
{
perror("read fail ");
return ;
}
if(write(fd,tmp,size) == 0)
{
perror("write client failed ");
return ;
}
bzero(tmp,READ_SIZE);
}
}
客户端的小程序:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define SER_IP "127.0.0.1"
#define SER_PORT 9877
#define MAX_LINE 128
void send_myself(int fd)
{
char tmp[MAX_LINE] = {0};
int size;
fgets(tmp,MAX_LINE - 1,stdin);
write(fd,tmp, strlen(tmp));
bzero(tmp,MAX_LINE);
//write(STDOUT_FILENO, tmp, MAX_LINE);
//write(STDOUT_FILENO,"\n",strlen("\n"));
if((size = read(fd,tmp,MAX_LINE -1)) == 0)
{
perror("read serv fail");
return ;
}
//tmp[size] = '\n';
//fputs(tmp,stdout);
//write(STDOUT_FILENO, tmp, strlen(tmp));
printf("size is %d\n",size);
printf("%s",tmp);
fflush(stdout);
return ;
}
int main(int argc, char** argv)
{
int send_sk;
struct sockaddr_in s_addr;
socklen_t len = sizeof(s_addr);
send_sk = socket(AF_INET, SOCK_STREAM, 0);
if(send_sk == -1)
{
perror("socket failed ");
return -1;
}
bzero(&s_addr, sizeof(s_addr));
s_addr.sin_family = AF_INET;
//s_addr.sin_addr.s_addr = htonl(
inet_pton(AF_INET,SER_IP,&s_addr.sin_addr);
s_addr.sin_port = htons(SER_PORT);
if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1)
{
perror("connect fail ");
return -1;
}
while(1)
{
send_myself(send_sk);
}
}
当服务器启动的时候进入监听状态,服务器刚好执行完socket、bind、listen、阻塞在accept上等待客户端的连接(属于被动连接)

当客户端在本地启动的时候,与服务器进行了连接,服务器accept返回,这个时候,就建立完连接,同时呢,服务器,父进程再次阻塞在accept这个函数调用上,这个时候,等在客户端的再一次连接

这个时候查看一下进程就会知道,服务器的子进程和父进程,如下图

当我们结束客户端的时候,立即查看服务和客户端的状态,如下图,如图所示,这个客户端进入了TIME_WAIT状态

由于没有对SIGCHLD信号进行处理是子进程变成僵尸进程了,如下图

僵尸进程状态是z(zombie)“+”代表的是前台进程,加入信号处理程序之后,就不会是子进程处于僵尸状态(哎!太可怕了)
当把与客户端连接的子进程kill掉的时候,客户端进入到CLOSE_WAIT状态,这个时候,那个子进程还没有收到客户端的ack信息,此时处于FIN_WAIT状态

过了一段时候后,子进程收到客户端的ack信息,半关闭就完成了,子进程就结束了如下图:
此时,如果客户端再次想那个子进程发送数据的时候,因为此时这个进程已经结束,在这个端口和ip上没有了进程,导致服务器会发送一个RST数据报,结束客户端程序
如果客户端已经接受到一个RST数据报的时候,这个时候在向服务器端发送数据的时候,内核会向客户端进程发送一个SIGPIPE信号,是客户端进程结束。
服务器主机崩溃的状态
如果,客户端和服务器已经建立了连接的时候,此时服务器崩溃(达到这一标准可以把服务器的网线拔掉,这个时候,服务器就不能发送FIN数据报了,和关机不一样的)
此时如果客户端向服务器发送数据的时候,因为服务器已经不存在了,那么客户端就不能接受到服务器给客户端的ack信息,这个时候,客户端建立的是TCP连接,就会重发数据报,发送多少次之后就会返回超时,前面所说的ETIMEOUT
服务器主机崩溃后重启:
当客户端和服务器已经建立连接的时候,服务器发生崩溃,重新启动的时候,丢失了原来和客户端的连接信息,这个时候,当客户端向服务器发送数据的时候(客户端并不知道,服务器已经忘记三次握手了),此时服务器发送RST数据报,就结束了客户端的发送
服务器关机:
会断开tcp连接,会发送FIN数据报