TCP回射 服务端程序
本例为多进程的 TCP 回射程序(服务端)
#include <unp.h>
int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
// 指定协议族(AF_INET: ipv4 协议),
// 指定套接字类型(TCP 数据流, UDP 数据报或者为原始套接字SOCK_STREAM: 基于 tcp),
// 最后一个选项默认即可
// 成功返回一个非负的套接字描述符(socket descriptor), 此时 listenfd 是一个将要
// 主动调用 connect 函数的客户端套接字(主动套接字)
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
// 初始化服务端套接字
bzero(&servaddr, sizeof(servaddr));
// 套接字规范中, 仅需要协议族, ip 地址和端口号三个属性
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
// 本地协议地址赋予套接字(端口相关, 初步理解为进程赋予众所周知的端口)
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
// 将 listenfd 转换成被动套接字, 指示内核, 此套接字可被调用, 此时, 服务端监听开始!
// 客户端理所应当的免去这一步
Listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
// 返回已建立连接的客户端套接字, 如果当前没有套接字完成连接, 函数阻塞
// 本函数循环调用, 直到所有建立连接的客户端套接字服务完毕, 此时阻塞,
// 等待新的连接建立.
// 客户端的套接字被存放于 cliaddr 中.
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
// 核心!!!
// 运行到此, 说明已经成功获得(否则阻塞与 accept 中) connfd, 但是本
// 主进程并不处理客户端连接, 而是通过 fork() 复制一个完全相同的子进程
// 服务客户套接字.
// 服务端调用 fork() 时, 会返回一个非零的子进程 id, 故而, if 中的语句永远
// 不会被父进程执行, 而被复制而来的子进程运行到这里的时候, fork() 函数不会复制
// 新的子进程, 而是返回 0, 即, c 中, fork() 函数一次调用, 会有两次返回值.
if ( (childpid = Fork()) == 0) { /* 子进程 */
// 子进程拿到 connfd 连接之后,
// 先关闭监听套接字, 子进程无需监听, 全权交由父进程, 而后由父进程分发请求即可
Close(listenfd);
str_echo(connfd); /* 处理请求 */
exit(0); // 子进程到此, 已完成使命, 终止退出即可, 此时 connfd 默认 close
}
Close(connfd); /* 父进程断开连接 */
}
}
void str_echo(int sockfd)
{
ssize_t n;
char buf[MAXLINE];
again:
while ( (n = read(sockfd, buf, MAXLINE)) > 0)
Writen(sockfd, buf, n);
if (n < 0 && errno == EINTR)
goto again;
else if (n < 0)
err_sys("str_echo: read error");
}
- 套接字 sockaddr_in cliaddr, servaddr
网际套接字地址结构, 包含协议族(ipv 4, ipv 6), 端口号, ip 地址
TCP回射 客户端程序
本例为多进程的 TCP 回射程序(客户端)
#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);
// ip 格式转换 点分十进制和二进制整数
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
// 本地套接字连接服务端套接字, 此时客户端会自行分配端口
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
// 连接成功, 执行操作即可
str_cli(stdin, sockfd); /* 输入 */
exit(0);
}
void
str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recvline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Writen(sockfd, sendline, strlen(sendline));
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
}
- 代码为 unix 网络编程 的示例代码
- 搬运与 TCP回射 服务端程序