您的位置:首页 > 理论基础 > 计算机网络

《UNIX网络编程》TCP网络编程基础(1)

2015-12-29 14:54 656 查看
最近发现只看书不打代码真的不行,所以还是决定自己把代码敲一遍,加深印象!

实验内容:

服务器和客户端通过tcp通信;

客户端从标准输入获得字符串,发送给服务器;

服务器统计收到的字符串长度,将结果返回给客户端;

客户端显示服务器返回的结果。

源代码:

/*
* tcp_server.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/in.h>
#include <string.h>
#include <signal.h>

#define PORT 8888
#define LISTENQ 2
#define BUFSIZE 1024

typedef void (*sighandler_t)(int);

/*向信号signum注册一个void (*sighandler_t)(int)类型
*的函数,函数句柄为handler
*使用typedef简化
*/
sighandler_t signal(int signum, sighandler_t handler);
/*处理子进程的终止信号,防止僵死进程*/
void sig_chld(int sign);

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

if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)  //监听套接字
{
printf("socket error\n");
return -1;
}

bzero(&servaddr, sizeof(servaddr)); //清零
servaddr.sin_family = AF_INET;
/*地址和端口都要转成网络字节序*/
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   //本地地址
servaddr.sin_port = htons(PORT);

/*绑定地址结构到套接字描述符*/
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
{
printf("bind error\n");
return -1;
}

/*监听, LISTENQ为监听队列的长度*/
if (listen(listenfd, LISTENQ) < 0)
{
printf("listen error\n");
return -1;
}

/*主循环*/
for( ; ; )
{
clilen = sizeof(cliaddr);
/*接收客户端连接*/
connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen);
if (connfd < 0)
continue;
/*fork子进程来处理请求*/
if ( (childpid = fork()) == 0 )
{
close(listenfd);    //子进程关闭监听套接字
process_conn_server(connfd);
exit(0);
}
close(connfd);  //父进程关闭连接套接字
}
}

void sig_chld(int sign)
{
pid_t   pid;
int     stat;
/*注意信号是不排队的,使用wait的话,同时多个信号只处理一个.
*使用waitpid来取得所有已终止子进程的状态;
*指定WNOHANG,告诉waitpid在有未终止的子进程运行时不要阻塞;
*不能在循环中调用wait,因为它会阻塞.
*/
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
{
printf("child %d terminated\n", pid);
}
return;
}


/*
*tcp_client.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/in.h>
#include <string.h>
#include <signal.h>

#define PORT 8888
#define BUFSIZE 1024

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);
void sig_pipe(int sign);

int
main (int argc, char **argv)
{
struct sockaddr_in  servaddr;
int             clifd;
signal(SIGPIPE, sig_pipe);

if (argc != 2)
{
printf("usage: client server_addr\n");
exit(1);
}

/*设置服务器地址*/
bzero(&servaddr, sizeof(servaddr)); //清零
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   //本地地址
servaddr.sin_port = htons(PORT);

if ((clifd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("socket error\n");
return -1;
}
/* 将用户输入的字符串类型的IP地址转为整型,
* p: presentation 表达格式 ASCII串
* n: numeric 数值格式 二进制
* 第二个参数为字符串指针, 第三个参数为指向结构struct in_addr的指针
*/
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
if (connect(clifd, (struct sockaddr*) &servaddr, sizeof(struct sockaddr)) < 0)
{
printf("connect error\n");
return -1;
}
process_conn_client(clifd);
close(clifd);
}

/*当服务器一端关闭时,客户端如果继续向服务器发送数据,将会
*收到一个SIGPIPE信号,我们在这里捕捉该信号.
*/
void sig_pipe(int sign)
{
printf("Catch a SIGPIPE signal\n");
}


/*
*tcp_process.c
*/
#include <sys/types.h>

void process_conn_server(int fd)
{
ssize_t     size = 0;
char        buffer[1024];

for (;;)
{
size = read(fd, buffer, 1024);
if (size == 0)
return ;
sprintf(buffer, "%d bytes altogether\n", size);
write(fd, buffer, strlen(buffer)+1);
}
}

void process_conn_client(int fd)
{
ssize_t     size = 0;
char        buffer[1024];

for (;;)
{
/*从标准输入读数据*/
size = read(0, buffer, 1024);
if (size > 0)
{
write(fd, buffer, size);    //写数据给服务器
size = read(fd, buffer, 1024);  //从服务器读数据
write(1, buffer, size); //输出到标准输出
}
}
}


编写Makefile:

CC = gcc

all:client server               #all规则,它依赖于client和server规则

client:tcp_process.o tcp_client.o   #client规则,生成客户端可执行程序

$(CC) -o client tcp_process.o tcp_client.o

server:tcp_process.o tcp_server.o   #server规则,生成服务器端可执行程序

$(CC) -o server tcp_process.o tcp_server.o

clean:                              #清理规则,删除client,server和中间文件

rm -f client server *.o


编译运行:

$ make
$ ./server &
$ ./client 127.0.0.1


清除中间文件及可执行文件:

$ make clean


一些说明

htonl()函数: 将一个32位数从主机字节顺序转换成网络字节顺序。

htons()函数:将一个16位数从主机字节顺序转换成网络字节顺序。

inet_pton()函数:将“点分十进制” -> “二进制整数”,同时适用于IPv4和IPv6。

其中:

p: presentation 表达格式 ASCII串;

n: numeric 数值格式 二进制

在 bind 和 accept 等函数中,需要将IPv4的地址结构sockaddr_in转换成通用地址结构sockaddr。

我们使用fork产生子进程来处理请求,父进程对子进程的结束进行处理,否则会产生僵死进程。处理的信号为SIGCHLD,注意处理使用waitpid而不是wait,如果只调用一次wait,由于信号不排队,所以当有多个子进程同时死亡,发送给父进程的多个SIGCHLD只会处理1次,如果循环调用wait,在还有未结束子进程的情况下,父进程将阻塞在wait上。

当服务器已经关闭,如果客户端试图向套接字写入数据,则会产生一个SIGPIPE信号,此时将造成程序的非正常退出。我们可以捕捉该信号,并进行一些善后工作。

服务器fork子进程之后,父进程应该关闭连接套接字,否则当有其他连接到来,将可能耗尽所有的套接字描述符。另外,子进程应关闭监听套接字。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: