Labweek9
实验内容1:
进程间通信 — 管道和 socket 通信
编译运行课件 Lecture11 示例代码 alg.11-1, alg.11-2.1, alg.11-2.2,指出你认为不合适的地方并加以改进。
alg.11-1-socket-port.c
程序细节说明
int socket(int domain, int type, int protocol)
创建一个socket的描述符,成功返回套接字描述符sockfd,失败返回-1。
domain是协议族,常用的有 AF_INET(IPV4协议)、AD_INET6(IPV6协议)、AF_UNIX(单一Unix系统中进程间通信)、AF_LOCAL、AF_ROUTE等
type为套接口类型,常用的有SOCK_STREAM(流套接字,TCP协议)、SOCK_DGRAM(数据报套接字,UDP协议)、SOCK_RAW(原始套接字,提供原始网络协议存取)
protocol是协议类型,取0会自动选择type类型对应的协议,常用值有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
将一个地址族中的特定地址绑定给某个套接字。成功返回0,失败返回-1。
sockfd为之前socket() 创建的代表要绑定的套接字的描述符。
addr表示服务器的通信地址,代表指向绑定给sockfd的协议地址的结构体;
addrlen是参数addr的长度,可以接受多种类型的结构体,所以需要指定长度,可以直接用sizeof() 函数获得。
其中sockaddr 结构体如下。
struct sockaddr {
unsigned short sa_family; /* 2 bytes, socket address family, AF_xxx */
char sa_data[14]; /* 14 bytes, protocol address */
};
有个与之类似的地址结构
struct sockaddr_in
{
short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/
unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
};
socklen_t 是一种类似int的数据类型。在64位机下,size_t(32bits)和int(64 bits)的长度是不一样的,但该函数的第三个参数的长度必须和int的长度相同,于是便有了socklen_t类型。
int getsockname(int sockfd, struct sockaddr *restrict_addr, socklen_t *restrict_addrlen)
用于作用获取一个套接字的名字,成功返回0,失败返回-1。
sockfd为需要获取名称的套接字的标识符
restrict_addr是存放所获取的套接字名称的结构体
restrict_addrlen是参数addr的长度
uint16_t ntohs(uint16_t netshort);
将一个无符号短整型数从网络字节顺序转换为主机字节顺序。
netshort为一个以网络字节顺序表达的16位数。
uint32_t htonl(uint32_t hostlong);
将主机数转换成无符号长整形的网络字节顺序。
hostlong为主机字节顺序表达的32位数。
alg.11-1-socket-port.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* #include <arpa/inet.h>
uni32_t htonl(uni32_t hostlong); // host to net long int
uni16_t htonl(uni16_t hostshort); // host to net short int
uni32_t ntohl(uni32_t netlong); // net to host long int
uni16_t ntohl(uni16_t netshort); // net to host short int
unsigned long inet_addr(const char* cp); // cp e.g., "192.168.10.130"
char* inet_ntoa(struct in_addr in);
*/
int main(void)
{
unsigned short port = 0;
int sockfd, ret, ret_val = 1;
struct sockaddr_in myaddr;
socklen_t addr_len;
char ip_name_str[INET_ADDRSTRLEN];
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* AF_INET: ipv4 */
if(sockfd == -1) {
perror("socket()");
return EXIT_FAILURE;
}
myaddr.sin_family = AF_INET; /* ipv4 */
myaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* INADDR_ANY: 0 */
myaddr.sin_port = 0; /* if .sin_port is set to 0, a free port number allocated by bind() */
addr_len = sizeof(myaddr);
ret = bind(sockfd, (struct sockaddr *)&myaddr, addr_len);
/* bind() not return the port number to myaddr: bind(int, const struct sockaddr, socklen_t)
use getsockname() to get the allocated port number */
if(ret == 0) {
addr_len = sizeof(myaddr);
ret = getsockname(sockfd, (struct sockaddr *)&myaddr, &addr_len);
if(ret == 0) {
port = ntohs(myaddr.sin_port);
printf("port number = %d\n", port);
strcpy(ip_name_str, inet_ntoa(myaddr.sin_addr));
printf("host addr = %s\n", ip_name_str);
} else {
ret_val = 0;
}
} else {
ret_val = 0;
}
ret = close(sockfd); /* close() defined in <unistd.h> */
if(ret != 0) {
ret_val = 0;
}
return ret_val;
}
程序先ipv4协议创建了socket描述,若成功,则先设置好myaddr的协议,port设为0。在用bind() 将其与sockfd绑定。
若成功,再求出myaddr的长度,然后用getsockname() 得到获取套接字的名字存入myaddr中,若成功
输出主机字节顺序的port及myaddr中存的ip地址。
可见新建了一个port number 为33479的socket。
主机没有ip地址,故为0。
alg.11-2.1-socket-server.c
程序细节
int listen(int sockfd, int backlog)
在使用socket()、bind() 函数建立套接字并绑定后,套接字默认是主动类型,该函数的作用是监听这个套接字,并把这个套接字变成被动类型,等待来自客户端的连接请求
sockfd为监听的套接字的描述符
backlog表示相应的套接字可以排队的最大连接个数
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
客户端可以通过调用这个函数和服务器发出连接请求建立连接
sockfd为客户端socket描述符
serv_addr为服务器的socket地址
addrlen是套接字地址的长度
int accept(int sockfd, struct sockaddr restrict *addr, socklen_t restrict *addrlen)
服务器用listen()监听到客户端通过connect()函数发出的连接请求后,可以建立连接,接受队列的第一个请求。
sockfd为服务器的套接字描述符,也称监听套接字
addr表示客户端的通信地址
addrlen是addr的长度
ssize_t send(int sockfd, const void *buf, socklen_t len, int flags)
用于向一个处在连接状态的套接字发送数据
sockfd为套接字描述符
buf表示存放要发送数据的一个缓冲区
len表示缓冲区中要发送的字节数
flags为执行方式,一般是0,常用的有MSG_CONFIRM(用来告诉链路层),MSG_DONTWAIT(启用非阻塞操作),MSG_DONTROUTE(只发送到直接连接的主机上)等
ssize_t recv(int sockfd, void *buf, socklen_t len, int flags)
令服务端或客户端可以从另一端接收数据
sockfd为套接字描述符
buf表示存放接收来的数据的一个缓冲区
len表示缓冲区中数据的字节数
flags表示调用执行方式,一般置0
int getifaddrs(struct ifaddrs **ifap)
获取本地网络接口信息,储存在一个链表中,链表的头结点的地址保存在*ifap中
void freeifaddrs(struct ifaddrs *ifap)
将之前使用getifaddrs() 函数动态分配内存获得的参数ifap释放
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
设置套接口的选项值
sockfd为要设置的socket描述符
level代表选项值定义的层次,值包括SOL_SOCKET和IPPROTO_TCP
optname要设置的套接口的选项
optval是指向存放设置的选项值的缓冲区,是一个指针
optlen是之前的参数optval的长度
struct hostent *gethostbyname(const char *name);
返回对应于给定主机名的主机信息。
name指向主机名的指针。
返回类型
struct hostent
{
char *h_name;
char ** h_aliases;
short h_addrtype;
short h_length;
char ** h_addr_list;
};
alg.11-2.1-socket-server.c
/* one client, one server, asynchronous send-receive */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/signal.h>
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
/* get a string of length n-1 from stdin and clear the stdin buffer */
char* s_gets(char* stdin_buf, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(stdin_buf, n, stdin);
if(ret_val) {
while(stdin_buf[i] != '\n' && stdin_buf[i] != '\0') {
i++;
}
if(stdin_buf[i] == '\n') {
stdin_buf[i] = '\0';
} else {
while (getchar() != '\n') ;
}
}
return ret_val;
}
/* save the ipv4 address of this server to ip_addr */
int getipv4addr(char *ip_addr)
{
struct ifaddrs *ifaddrsptr = NULL;
struct ifaddrs *ifa = NULL;
void *tmpptr = NULL;
int ret;
ret = getifaddrs(&ifaddrsptr);
if (ret == -1) {
ERR_EXIT("getifaddrs()");
}
for(ifa = ifaddrsptr; ifa != NULL; ifa = ifa->ifa_next) {
if(!ifa->ifa_addr) {
continue;
}
if(ifa->ifa_addr->sa_family == AF_INET) { /* AF_INET: ipv4 */
tmpptr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
char addr_buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, tmpptr, addr_buf, INET_ADDRSTRLEN);
printf("%s ipv4 address = %s\n", ifa->ifa_name, addr_buf);
if (strcmp(ifa->ifa_name, "lo") != 0) {
strcpy(ip_addr, addr_buf); /* save the ipv4 address */
}
} else if(ifa->ifa_addr->sa_family == AF_INET6) { /* ipv6 */
tmpptr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
char addr_buf[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, tmpptr, addr_buf, INET6_ADDRSTRLEN);
printf("%s ipv6 address %s\n", ifa->ifa_name, addr_buf);
}
}
if (ifaddrsptr != NULL) {
freeifaddrs(ifaddrsptr);
}
return EXIT_SUCCESS;
}
int main(void)
{
int server_fd, connect_fd;
struct sockaddr_in server_addr, connect_addr;
socklen_t addr_len;
int recvbytes, sendbytes, ret;
char send_buf[BUFFER_SIZE], recv_buf[BUFFER_SIZE];
char ip_addr[INET_ADDRSTRLEN]; /* ipv4 address */
char stdin_buf[BUFFER_SIZE];
uint16_t port_num;
pid_t childpid;
server_fd = socket(AF_INET, SOCK_STREAM, 0); /* create an ipv4 server*/
if(server_fd == -1) {
ERR_EXIT("socket()");
}
printf("server_fd = %d\n", server_fd);
ret = getipv4addr(ip_addr); /* get server ipv4 address */
if (ret == EXIT_FAILURE) {
ERR_EXIT("getifaddrs()");
}
/* set sockaddr_in */
server_addr.sin_family = AF_INET; /* ipv4 */
server_addr.sin_port = 0; /* auto server port number */
server_addr.sin_addr.s_addr = inet_addr(ip_addr);
bzero(&(server_addr.sin_zero), 8); /* padding with 0s */
int opt_val = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val)); /* many options */
addr_len = sizeof(struct sockaddr);
ret = bind(server_fd, (struct sockaddr *)&server_addr, addr_len);
if(ret == -1) {
close(server_fd);
ERR_EXIT("bind()");
}
printf("Bind success!\n");
addr_len = sizeof(server_addr);
ret = getsockname(server_fd, (struct sockaddr *)&server_addr, &addr_len);
if(ret == 0) {
printf("Server port number = %d\n", ntohs(server_addr.sin_port));
} else {
close(server_fd);
ERR_EXIT("Server port number unknownn");
}
ret = listen(server_fd, MAX_QUE_CONN_NM);
if(ret == -1) {
close(server_fd);
ERR_EXIT("listen()");
}
printf("Server ipv4 addr: %s\n", ip_addr);
printf("Listening ...\n");
addr_len = sizeof(struct sockaddr);
/* addr_len should be refreshed before each accept() */
connect_fd = accept(server_fd, (struct sockaddr *)&connect_addr, &addr_len);
/* waiting for connection */
if(connect_fd == -1) {
close(server_fd);
ERR_EXIT("accept()");
}
port_num = ntohs(connect_addr.sin_port);
strcpy(ip_addr, inet_ntoa(connect_addr.sin_addr));
printf("Client accepted: IP addr = %s, port = %hu\n", ip_addr, port_num);
childpid = fork();
if(childpid < 0) {
close(server_fd);
close(connect_fd);
ERR_EXIT("fork()");
}
if(childpid > 0) { /* parent pro */
while(1) { /* sending cycle */
memset(send_buf, 0, BUFFER_SIZE);
s_gets(send_buf, BUFFER_SIZE); /* without endding '\n' */
sendbytes = send(connect_fd, send_buf, strlen(send_buf), 0); /* blocking send */
if(sendbytes <= 0) {
printf("sendbytes = %d. Connection terminated ...\n", sendbytes);
break;
}
if(strncmp(send_buf, "end", 3) == 0) {
break;
}
}
close(server_fd);
close(connect_fd);
kill(childpid, SIGKILL);
}
else { /* child pro */
while(1) { /* receiving cycle */
memset(recv_buf, 0, BUFFER_SIZE);
recvbytes = recv(connect_fd, recv_buf, BUFFER_SIZE, 0); /* blocking receive */
if(recvbytes <= 0) {
printf("recvbytes = %d. Connection terminated ...\n", recvbytes);
break;
}
printf("\t\t\t\t\tClient %s >>>> %s\n", ip_addr, recv_buf);
if(strncmp(recv_buf, "end", 3) == 0) {
break;
}
}
close(connect_fd);
close(server_fd);
kill(getppid(), SIGKILL);
}
return EXIT_SUCCESS;
}
程序第一个函数为读入一行,处理好格式,存入send_buf中。
第二个函数为获得本机的ipv4地址和ipv6地址,并输出一些反馈
先用socket函数创建一个socket描述符,得到server_fd
再得到本机的ip地址,并设置好server_addr结构体内的地址信息
然后用bind函数将server_fd与地址绑定
如图,程序输出了一些地址,到这一步显示绑定成功。
接着输出主机格式的port number及ipv地址
然后程序listen尝试连接到该server_fd的客户机
可见程序开始侦听尝试连接的信息。
再调用accept得到connect_fd,表示接受连接,成功后输出连接的客户机的ip地址和port number。
程序生成一个子进程,父进程中执行发送操作,通过send函数将读入的一行发送到socket处。若输入为0或为“end”则退出。
子进程执行接收,调用recv接收socket上的内容,若为空则退出,否则输出客户机的ip地址及收到的内容。
当父进程或子进程对应的循环退出后,都会杀死对方进程,并使整个程序结束。
可见服务器能收到客户机发送的内容“i love yukimaru”,并给客户机发送内容“fat tu”。
alg.11-2.2-socket-client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/signal.h>
#include <fcntl.h>
#include <sys/stat.h>
#define BUFFER_SIZE 1024
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
/* get a string of length n-1 from stdin and clear the stdin buffer */
char* s_gets(char* stdin_buf, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(stdin_buf, n, stdin);
if(ret_val) {
while(stdin_buf[i] != '\n' && stdin_buf[i] != '\0') {
i++;
}
if(stdin_buf[i] == '\n') {
stdin_buf[i] = '\0';
} else {
while (getchar() != '\n') ;
}
}
return ret_val;
}
int main(void)
{
int connect_fd, sendbytes, recvbytes, ret;
uint16_t port_num;
char send_buf[BUFFER_SIZE], recv_buf[BUFFER_SIZE];
char ip_addr[INET_ADDRSTRLEN], stdin_buf[BUFFER_SIZE];
char clr;
struct hostent *host;
struct sockaddr_in server_addr, connect_addr;
socklen_t addr_len;
pid_t childpid;
printf("Input server's hostname/ipv4: "); /* www.baidu.com or an ipv4 address */
scanf("%s", stdin_buf);
while((clr = getchar()) != '\n' && clr != EOF); /* clear the stdin buffer */
printf("Input server's port number: ");
scanf("%hu", &port_num);
while((clr = getchar()) != '\n' && clr != EOF);
host = gethostbyname(stdin_buf);
if(host == NULL) {
printf("invalid name or ip address\n");
exit(EXIT_FAILURE);
}
printf("server's official name = %s\n", host->h_name);
char** ptr = host->h_addr_list;
for(; *ptr != NULL; ptr++) {
inet_ntop(host->h_addrtype, *ptr, ip_addr, sizeof(ip_addr));
printf("\tserver address = %s\n", ip_addr);
}
/*creat connection socket*/
connect_fd = socket(AF_INET, SOCK_STREAM, 0);
if(connect_fd == -1) {
ERR_EXIT("socket()");
}
/* set sockaddr_in of server-side */
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port_num);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(server_addr.sin_zero), 8);
addr_len = sizeof(struct sockaddr);
ret = connect(connect_fd, (struct sockaddr *)&server_addr, addr_len); /* connect to server */
if(ret == -1) {
close(connect_fd);
ERR_EXIT("connect()");
}
/* connect_fd is allocated a port_number after connecting */
addr_len = sizeof(struct sockaddr);
ret = getsockname(connect_fd, (struct sockaddr *)&connect_addr, &addr_len);
if(ret == -1) {
close(connect_fd);
ERR_EXIT("getsockname()");
}
port_num = ntohs(connect_addr.sin_port);
strcpy(ip_addr, inet_ntoa(connect_addr.sin_addr));
printf("local socket ip addr = %s, port = %hu\n", ip_addr, port_num);
childpid = fork();
if(childpid < 0) {
close(connect_fd);
ERR_EXIT("fork()");
}
if(childpid > 0) { /* parent pro */
while(1) { /* sending cycle */
memset(send_buf, 0, BUFFER_SIZE);
s_gets(send_buf, BUFFER_SIZE); /* without endding '\n' */
sendbytes = send(connect_fd, send_buf, strlen(send_buf), 0); /* blocking send */
if(sendbytes <= 0) {
printf("sendbytes = %d. Connection terminated ...\n", sendbytes);
break;
}
if(strncmp(send_buf, "end", 3) == 0)
break;
}
close(connect_fd);
kill(childpid, SIGKILL);
}
else { /* child pro */
while(1) { /* receiving cycle */
memset(recv_buf, 0, BUFFER_SIZE);
recvbytes = recv(connect_fd, recv_buf, BUFFER_SIZE, 0); /* blocking receive */
if(recvbytes <= 0) {
printf("recvbytes = %d. Connection terminated ...\n", recvbytes);
break;
}
printf("\t\t\t\t\tserver %s >>>> %s\n", ip_addr, recv_buf);
if(strncmp(recv_buf, "end", 3) == 0)
break;
}
close(connect_fd);
kill(getppid(), SIGKILL);
}
return EXIT_SUCCESS;
}
该代码为客户机程序类似,定义的第一个函数与服务器程序相同,为读入一行。
程序先要求用户输入ipv4地址及port number。
通过gethostbyname函数得到主机信息。
通过socket函数得到标识符connect_fd
然后设置server_addr的各个参数
求出addr_len后用connect函数请求连接
在服务器程序成功accept后用getsockname() 获取套接字的名字。
然后输出主机格式的port number及对应的ip地址。
可见程序成功获得了两个端口的port number,得到了服务器的ip地址并输出。
程序生成一个子进程,父进程中执行发送操作,通过send函数将读入的一行发送到socket处。若输入为0或为“end”则退出。
子进程执行接收,调用recv接收socket上的内容,若为空则退出,否则输出客户机的ip地址及收到的内容。
当父进程或子进程对应的循环退出后,都会杀死对方进程,并使整个程序结束。
可见服务器能收到客户机发送的内容“i love yukimaru”,并给客户机发送内容“fat tu”。
在任意一方输入end后,都会将end传入socket,并结束自己的两个进程。
而对方接收到end字符串后,会杀死另一个进程并结束自己。
代码改进
改进了server的代码,使其可以连接两台客户机,在原来的基础上生成一个新的子进程,负责接收第二题客户机的信息。主进程执行发送消息,同时发送到两台服务器。
只需要修改server的代码,client可以直接沿用。
Server.c
/* two clients, one server, asynchronous send-receive */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/signal.h>
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
/* get a string of length n-1 from stdin and clear the stdin buffer */
char* s_gets(char* stdin_buf, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(stdin_buf, n, stdin);
if(ret_val) {
while(stdin_buf[i] != '\n' && stdin_buf[i] != '\0') {
i++;
}
if(stdin_buf[i] == '\n') {
stdin_buf[i] = '\0';
} else {
while (getchar() != '\n') ;
}
}
return ret_val;
}
/* save the ipv4 address of this server to ip_addr */
int getipv4addr(char *ip_addr)
{
struct ifaddrs *ifaddrsptr = NULL;
struct ifaddrs *ifa = NULL;
void *tmpptr = NULL;
int ret;
ret = getifaddrs(&ifaddrsptr);
if (ret == -1) {
ERR_EXIT("getifaddrs()");
}
for(ifa = ifaddrsptr; ifa != NULL; ifa = ifa->ifa_next) {
if(!ifa->ifa_addr) {
continue;
}
if(ifa->ifa_addr->sa_family == AF_INET) { /* AF_INET: ipv4 */
tmpptr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
char addr_buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, tmpptr, addr_buf, INET_ADDRSTRLEN);
printf("%s ipv4 address = %s\n", ifa->ifa_name, addr_buf);
if (strcmp(ifa->ifa_name, "lo") != 0) {
strcpy(ip_addr, addr_buf); /* save the ipv4 address */
}
} else if(ifa->ifa_addr->sa_family == AF_INET6) { /* ipv6 */
tmpptr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
char addr_buf[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, tmpptr, addr_buf, INET6_ADDRSTRLEN);
printf("%s ipv6 address %s\n", ifa->ifa_name, addr_buf);
}
}
if (ifaddrsptr != NULL) {
freeifaddrs(ifaddrsptr);
}
return EXIT_SUCCESS;
}
int main(void)
{
int server_fd, connect_fd,connect_fd2;
struct sockaddr_in server_addr, connect_addr,connect_addr2;
socklen_t addr_len;
int recvbytes, sendbytes, ret;
char send_buf[BUFFER_SIZE], recv_buf[BUFFER_SIZE];
char ip_addr[INET_ADDRSTRLEN]; /* ipv4 address */
char stdin_buf[BUFFER_SIZE];
uint16_t port_num;
pid_t childpid,childpid2;
server_fd = socket(AF_INET, SOCK_STREAM, 0); /* create an ipv4 server*/
if(server_fd == -1) {
ERR_EXIT("socket()");
}
printf("server_fd = %d\n", server_fd);
ret = getipv4addr(ip_addr); /* get server ipv4 address */
if (ret == EXIT_FAILURE) {
ERR_EXIT("getifaddrs()");
}
/* set sockaddr_in */
server_addr.sin_family = AF_INET; /* ipv4 */
server_addr.sin_port = 0; /* auto server port number */
server_addr.sin_addr.s_addr = inet_addr(ip_addr);
bzero(&(server_addr.sin_zero), 8); /* padding with 0s */
int opt_val = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val)); /* many options */
addr_len = sizeof(struct sockaddr);
ret = bind(server_fd, (struct sockaddr *)&server_addr, addr_len);
if(ret == -1) {
close(server_fd);
ERR_EXIT("bind()");
}
printf("Bind success!\n");
addr_len = sizeof(server_addr);
ret = getsockname(server_fd, (struct sockaddr *)&server_addr, &addr_len);
if(ret == 0) {
printf("Server port number = %d\n", ntohs(server_addr.sin_port));
} else {
close(server_fd);
ERR_EXIT("Server port number unknownn");
}
ret = listen(server_fd, MAX_QUE_CONN_NM);
if(ret == -1) {
close(server_fd);
ERR_EXIT("listen()");
}
printf("Server ipv4 addr: %s\n", ip_addr);
printf("Listening ...\n");
addr_len = sizeof(struct sockaddr);
/* addr_len should be refreshed before each accept() */
connect_fd = accept(server_fd, (struct sockaddr *)&connect_addr, &addr_len);
/* waiting for connection */
if(connect_fd == -1) {
close(server_fd);
ERR_EXIT("accept()");
}
port_num = ntohs(connect_addr.sin_port);
strcpy(ip_addr, inet_ntoa(connect_addr.sin_addr));
printf("Client accepted: IP addr = %s, port = %hu\n", ip_addr, port_num);
printf("input 0 to continue, or input 1 to add another client\n");
int ops;
scanf("%d",&ops);
if(ops==1)
{
connect_fd2 = accept(server_fd, (struct sockaddr *)&connect_addr2, &addr_len);
/* waiting for connection */
if(connect_fd2 == -1)
{
close(server_fd);
ERR_EXIT("accept()");
}
port_num = ntohs(connect_addr2.sin_port);
strcpy(ip_addr, inet_ntoa(connect_addr2.sin_addr));
printf("Client accepted: IP addr = %s, port = %hu\n", ip_addr, port_num);
}
childpid = fork();
if(childpid < 0) {
close(server_fd);
close(connect_fd);
ERR_EXIT("fork()");
}
if(childpid > 0) {
if (ops==1)
{
childpid2=fork();
if(childpid < 0)
{
close(server_fd);
close(connect_fd);
ERR_EXIT("fork()");
}
if( childpid2==0)
{
while(1)
{ /* receiving cycle */
memset(recv_buf, 0, BUFFER_SIZE);
recvbytes = recv(connect_fd2, recv_buf, BUFFER_SIZE, 0); /* blocking receive */
if(recvbytes <= 0) {
printf("recvbytes = %d. Connection terminated ...\n", recvbytes);
break;
}
printf("\t\t\t\t\tClient %s >>>> %s\n", ip_addr, recv_buf);
if(strncmp(recv_buf, "end", 3) == 0) {
break;
}
}
close(connect_fd2);
close(connect_fd);
close(server_fd);
kill(getppid(), SIGKILL);
kill(childpid,SIGKILL);
}
}
/* parent pro */
while(1) { /* sending cycle */
memset(send_buf, 0, BUFFER_SIZE);
s_gets(send_buf, BUFFER_SIZE); /* without endding '\n' */
sendbytes = send(connect_fd, send_buf, strlen(send_buf), 0); /* blocking send */
if (ops==1)
sendbytes = send(connect_fd2, send_buf, strlen(send_buf), 0);
/* if(sendbytes <= 0) {
printf("sendbytes = %d. Connection terminated ...\n", sendbytes);
break;
}*/
if(strncmp(send_buf, "end", 3) == 0) {
break;
}
}
close(server_fd);
close(connect_fd);
kill(childpid, SIGKILL);
}
else { /* child pro */
while(1) { /* receiving cycle */
memset(recv_buf, 0, BUFFER_SIZE);
recvbytes = recv(connect_fd, recv_buf, BUFFER_SIZE, 0); /* blocking receive */
if(recvbytes <= 0) {
printf("recvbytes = %d. Connection terminated ...\n", recvbytes);
break;
}
printf("\t\t\t\t\tClient %s >>>> %s\n", ip_addr, recv_buf);
if(strncmp(recv_buf, "end", 3) == 0) {
break;
}
}
close(connect_fd);
close(server_fd);
kill(getppid(), SIGKILL);
}
return EXIT_SUCCESS;
}
在成功接收到一个客户机进程的连接请求后,会询问是否连接多一个的客户机。输入1表示连接。
连接成功为会计算并输出新客户机的port number
随后生成一个子进程接收客户机1的信息。
若有客户机2,则生成子进程2执行接收客户机2的信息。
并且在无论哪方发送了end,都会送到socket中,并结束所有进程。
可见服务器分别accept到两台客户机的通信请求,分别由port number52226和52228.
服务器能接受到两台客户机的消息,并能传输信息“do you love debubu”并分别得到“debubu kawaii”和“yes i do”的回答,能模拟一个简易的群组聊天的场景。
在客户机2输入end后,全部程序几乎同时结束。