⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tcp 客户-服务器例子.txt

📁 学习(编程技巧_编程知识_程序代码),是学习编程不可多得的学习精验
💻 TXT
字号:
TCP 客户-服务器例子
 

--------------------------------------------------------------------------------
 
第八军团 时间:2004-1-17 21:34:58 
   
概述
回射服务器:echo server 

7th 端口; /etc/inetd.conf 

1.客户从标准输入读一行文本,写到服务器上

2.服务器从网络输入读此行,并回射给客户

3.客户读此回射行并写到标准输出。


--------------------------------------------------------------------------------

几个读写有关的函数
fputs 和 fgets 标准库函数

//简单的输入和输出程序:

// mystr_client.c
#include "unp.h"
void main(void)
{
char sendline[MAXLINE],recevline[MAXLINE];
fgets(sendline, MAXLINE, stdin); //标准输入
memcpy(recevline, sendline, strlen(sendline));
fputs(recevline, stdout); //标准输出
}

//writen 和 readline字节流读写函数

ssize_t Writen(int filedes, const void *buff, size_t nbytes);
ssize_t Readline(int filedes, void *buff, size_t maxlen);


--------------------------------------------------------------------------------

TCP回射服务器程序
//main源程序:

#include "unp.h"

int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;

listenfd = Socket(AF_INET, SOCK_STREAM, 0);//创建套接口

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);//9877

Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));//绑定端口

Listen(listenfd, LISTENQ);

for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);//等待客户完成连接accept

if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}



//服务器程序str_echo 函数

//读如一行 Readline

//回射此行 Writen

#include "unp.h"
#include "sum.h"

void
str_echo(int sockfd)
{
ssize_t n;
struct args args;
struct result result;

for ( ; ; ) {
if ( (n = Readn(sockfd, &args, sizeof(args))) == 0)
return; /* connection closed by other end */

result.sum = args.arg1 + args.arg2;
Writen(sockfd, &result, sizeof(result));
}
}


--------------------------------------------------------------------------------

TCP回射客户端程序
//main创建套接口,装填IP地址结构socket bzero与服务器连接connect

#include "unp.h"

int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;

if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");

sockfd = Socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

str_cli(stdin, sockfd); /* do it all */

exit(0);
}


//tcp回射客户程序str_cli函数

#include "unp.h"

void
str_echo(int sockfd)
{
long arg1, arg2;
ssize_t n;
char line[MAXLINE];

for ( ; ; ) {
if ( (n = Readline(sockfd, line, MAXLINE)) == 0)
//从服务器读回射行

return; /* connection closed by other end */

if (sscanf(line, "%ld%ld", &arg1, &arg2) == 2)
snprintf(line, sizeof(line), "%ld\n", arg1 + arg2);
else
snprintf(line, sizeof(line), "input error\n");

n = strlen(line);
Writen(sockfd, line, n);//写到服务器
}
}


--------------------------------------------------------------------------------

正常启动

服务器后台启动 ./tcpserv01 &

检查监听套接口状态 netstat –a

过滤:netstat –a |grep 9877

启动客户端程序 ./tcpcli01 127.0.0.1

检查TCP的连接 netstat –a

过滤:netstat –a |grep 9877

检查进程的状态和关系 ps –l

tty控制台 ; pts/0伪终端

pid进程编号 ppid 父进程编号

父进程的ppid 是shell (bash)

状态S 

I 空闲 Idle S 停止 stop Z 僵尸 zombie

状态 WCHAN

键入 EOF (Control-D)结束


TCP连接终止序列
客户端键入EOF字符

fgets返回一个空指针

str_cli返回main 

main通过调用exit终止

客户套接口由内核关闭

客户TCP发送一个FIN给服务器

服务器TCP则以ACK响应

此时,服务器套接口处于CLOSE—WAIT状态,客户套接口处于FIN—WAIT2状态

服务器TCP接收FIN时,服务器子进程阻塞于readline调用.readline返回0

函数str_echo返回服务器子进程的main

服务器子进程通过调用exit来终止

服务器子进程中打开的所有描述字随之关闭。

由于进程来关闭已连接套接口引发TCP连接终止序列的最后两个分节:

一个从服务器到客户的FIN和一个从客户到服务器的ACK。至此,连接完全终止,

客户套接口进入TIME—WAIT状态。

进程终止处理的另一部分是在服务器子进程终止时给父进程发一个信号SIGCHLD。

但我们在代码中未捕获,于是出现了:僵尸进程 zombie process,用

ps -la 查看


--------------------------------------------------------------------------------

Posix 信号处理
信号signal:某事件发生时对进程的通知(软中断)

信号是异步的不可提前知道信号发生的时间

信号的流向:进程->进程(本身)    内核->进程

SIGCHLD: 内核在某进程终止时发给父进程的信号

每个信号都有处理方法disposition /action


处理函数sigaction的选择

信号处理程序(signal handler)来捕获(catching)信号.

void handler(int signo);SIGKILL , SIGSTOP不能被捕获


设置信号的处理办法为SIG—IGN来忽略信号;SIGKILL , SIGSTOP不能被捕获

设置信号的处理办法为SIG_DFL来设置缺省处理办法,  在接收到信号时终止进程

 特定信号还在当前工作目录产生一个进程的核心映像。

个别信号的缺省处理办法是忽略,SIGCHLD就是缺省处理办法是忽略的信号。

signal 函数

通用的信号处理函数:sigaction,signal

为了方便起见,建立新的信号处理函数signal

Sigfunc *signal(int signo,Sigfunc *func);


/lib/ signal.c /desktop/signal.txt


一旦安装了信号处理程序,它便一直安装着。

当一个信号处理程序正在执行时,所递交的信号是阻塞的除了被捕获的信号外,

没有额外信号阻塞。如果一个信号在阻塞时生成了一次或多次,在信号解阻塞后

一般只递送一次。缺省时Unix信号是不排队的有选择性地阻塞或不阻塞一组信号

是可能的可以在某段临界区代码执行时,不许捕获某些信号, 以此来保护这段代码。


--------------------------------------------------------------------------------

处理SIGCHLD 信号
设置僵尸(zombie)状态的目的:维护子进程的信息,以便父进程在稍后的某个时候取回。

僵尸(zombie))状态的信息:子进程的ID 终止状态

子进程的资源利用信息(CPU时间、内存等等)。

如果一个进程终止,且该进程有子进程处于僵尸状态,则所有僵尸子进程的父进程ID均

置为1(init进程)。

init进程将作为这些子进程的继父并负责清除它们(init进程将wait它们,从而去除僵尸

进程)。僵尸进程输出的COMMAND列为<defunt>(ps命令输出)。

僵尸进程的危害:占用内核空间,最终导致无法工作。

处理僵尸进程

处理SIGCHLD的Signal 函数在listen之后, accept之后:

void Signal (SIGCHID, sig_chld)
{
pid+t pid;
int stat;
pid= wait(&stat);
prtinf(“child %d terminated\n”, pid);
return;
}

处理步骤

键入EOF字符来终止客户,客户TCP发一个FIN给服务器,服务器以ACK响应。

FIN的接收导致服务器TCP递送一个EOF给子进程阻塞中的readline,从而子进程终止。

当信号SIGCHLD递交时,父进程阻塞于accept调用。函数sig_chld(信号处理程序)执

行,它调用函数wait取到子进程的PID和终止状态,再调用printf.然后返回。

由于该信号是在父进程阻塞于慢系统调用(accept)时由父进程铺获的,所以内核将

使accept返回一个EINTE错误(被中断的系统调用).而父进程不处理此错误,所以中

止。//有些系统。



--------------------------------------------------------------------------------

wait 和waitpid 函数
pid_t wait (int *statloc);
pid_t waitpid(pid_t pid,int *statloc, int options);

二者均返回:进程ID为0成功,或-1出错

如果没有终止的子进程让进程来调用wait,但有一个或多个正在执行的子进程,则阻塞

直到第一个现有子进程终止。

waitpid对等待哪个进程及是否阻塞给了我们更多的控制:参数pid让指定想等待的进程ID,

值-1表示等待第一个终止的子进程。参数option指定附加选项。最常用的选项是wNOHANG,

它通知内核在没有己终止子进程时不要阻塞。

调用wait并不足以防止出现僵尸进程:

5个信导都在信号处理程序执行之前产生,信号处理程序又只执行一次,因为unix 信号

一般是不排对的。更严重的是,此问题是不确定的,信号处理程序可能执行三次或四次。

在我们刚刚运行的例子中,客户与 服务器在同一主机上,信号处理程序执行一次,留下

四个僵尸进程。但若我们在不同的主机 上运行客户和服务器,信号处理程序一般执行两

次:一次作为第一个产生的信号的结果,由 于另外4个信号在信号处理程序执行时发生,

所以处理程序一般情况下去再被调用一次,这 就留下了三个僵尸进程。但有时,可能依

赖于FIN到达服务器主机的时机,信号处理程序执 行三次或四次。

正确的解决办法是调用watpid而不是wait.

在循环内调用waitpid取得了所有已终止子进程的状态。

指定选项WNOHANG,它告诉waitpid在有末终止的子进程运行 时不要阻塞。

不能在循环中调用wait,因为没有办法防止wait在有未终止的子进程运行时阻塞。


--------------------------------------------------------------------------------

关于数据格式的问题
客户和服务器之间传递文本

在本地转换和处理文本,不论客户和服务器主机的字节序如何,客户和服务器都能工

作得很好。

在客户与服务器之间传递二进制结构潜在的问题:

不同的实现以不同的格式存储二进制数,最常用的方法便是大端格式与小端格式

不同的实现在存储相同的C数据类型时可能不同。32bits/64bits?

不同的实现给结构打包的方式也是不同的,取决于所用数据类型的垃数及机器的对

齐限制,跨套接口来传送二进制结构是很不明智的。


解决此数据格式问题常用方法

把所有的数值数据作为文本串来传递要以两个主机有相同的字符集为基础。

显式定义所支持数据类型的二进制格式位效,大端或小端,在客户与服务器之间以此

格式传递所有数据。

 
 
 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -