主要摘自《深入理解计算机系统》一书,略作整理,加了些备注。可以简单了解一下UNIX网络编程。下面程序已在Ubuntu9.10下测试通过。
客户端主程序:
#include "client.h" int main(int argc, char **argv) { int clientfd; //客户端套接字描述符 if(argc != 3) //参数必须是3个 { fprintf(stderr, "usage: %s <host> <port>\n", argv[0]); return 0; } clientfd = open_clientfd(argv[1], atoi(argv[2])); //打开连接 exchange_data(clientfd); //与服务器交换数据 close(clientfd); //关闭客户端套接字 return 0; }
主要包含了一个头文件,里面有具体的实现。下面是client.h的定义。其中所用的IO输入输出,就是代码开源(2)——UNIX 健壮I/O函数介绍的健壮IO,包含进来即可。这里就不重复给出了。
对客户端程序,当标准输入遇到EOF,或者因为用户在键盘键入ctrl-d,或者因为在一个重定向的输入文件中用尽了所有的文本行,循环就结束。这时客户端关闭套接字描述符,并发送一个EOF通知服务器,服务器就可以收到一个为0的返回码,从而确定客户端已经关闭。也就是说,当服务器和客户端都运行时,客户端要想退出,最简单的就是按ctrl-d。
#ifndef CLIENT_H_ #define CLIENT_H_ #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <netdb.h> #include "rio.h" #define MAX_LINE 1024 void unix_error(char *msg); //错误处理程序 void exchange_data(int fd); //与服务器交换数据 int open_clientfd(char *hostname, int port); //打开连接,外部调用 int _open_clientfd(char *hostname, int port); //打开连接,内部调用 //错误处理程序,打印错误信息 void unix_error(char *msg) { fprintf(stderr, "%s: %s\n", msg, strerror(errno)); exit(0); } //与服务器交换数据 void exchange_data(int fd) { char buf[MAX_LINE]; rio_t rio; rio_readinitb(&rio, fd); //初始化rio_t while(fgets(buf, MAX_LINE, stdin) != NULL) //从标准输入读入数据 { rio_writen(fd, buf, strlen(buf)); //发往服务器端 rio_readlineb(&rio, buf, MAX_LINE); //从服务器读一行 fputs(buf, stdout); //写到标准输出 } } //打开连接,外部调用 int open_clientfd(char *hostname, int port) { int rc; if((rc = _open_clientfd(hostname, port)) < 0) { if(rc == -1) unix_error("Open client UNIX error"); else unix_error("Open client DNS error"); } return rc; } //打开连接,内部调用 int _open_clientfd(char *hostname, int port) { int clientfd; //客户端套接字描述符 struct hostent *hp; //主机条目 struct sockaddr_in serveraddr; //服务器地址 if((clientfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) //返回-1表示出错 return -1; if((hp = gethostbyname(hostname)) == NULL) //返回空表示出错 return -2; bzero((char *)&serveraddr, sizeof(serveraddr)); //清零 serveraddr.sin_family = AF_INET; bcopy((char *)hp->h_addr, (char *)&serveraddr.sin_addr.s_addr, hp->h_length); //服务器IP地址 serveraddr.sin_port = htons(port); //转换为网络序 if(connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) //连接 return -1; printf("Connect to server success\n"); return clientfd; //连接成功 } #endif /* CLIENT_H_ */
下面给出服务器的主程序:
#include "server.h" int main(int argc, char **argv) { int listenfd, connfd; unsigned int clientlen; //地址长度 struct sockaddr_in clientaddr; //客户端地址 struct hostent *hp; char *haddrp; //客户端域名 if(argc != 2) //参数必须是2个 { fprintf(stderr, "usage: %s <port>\n",argv[0]); return 0; } listenfd = open_listenfd(atoi(argv[1])); //进入监听状态 clientlen = sizeof(clientaddr); while(1) //只支持单个连接 { if((connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen)) < 0) //建立连接 { fprintf(stderr, "Accept error"); exit(0); } hp = gethostbyaddr((const char *)&clientaddr.sin_addr.s_addr, sizeof(clientaddr.sin_addr.s_addr), AF_INET); //客户端域名 haddrp = inet_ntoa(clientaddr.sin_addr); //客户端IP地址 printf("%s (%s) connect to server\n", hp->h_name, haddrp); exchange_data(connfd); //与客户端交换数据 close(connfd); //客户端断开连接 printf("%s (%s) close\n", hp->h_name, haddrp); } return 0; }
主要包含了一个头文件,里面有具体的实现。下面是server.h的定义。
#ifndef SERVER_H_ #define SERVER_H_ #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <netdb.h> #include <arpa/inet.h> #include <errno.h> #include <unistd.h> #include "rio.h" #define MAX_LINE 1024 #define MAX_LISTEN 1024 //最大连接数 void unix_error(char *msg); //错误处理 void exchange_data(int connfd); //与客户端交换数据 int open_listenfd(int port); //打开监听,外部调用 int _open_listenfd(int port); //打开监听,内部调用 //错误处理程序,打印错误信息 void unix_error(char *msg) { fprintf(stderr, "%s: %s\n", msg, strerror(errno)); exit(0); } //与客户端交换数据 void exchange_data(int connfd) { size_t n; char buf[MAX_LINE]; rio_t rio; rio_readinitb(&rio, connfd); while((n = rio_readlineb(&rio, buf, MAX_LINE)) != 0) //读到末尾结束 { printf("server received %d bytes\n", n); //收到的字节数 rio_writen(connfd, buf, n); //反显到客户端 } } //打开监听 int open_listenfd(int port) { int rc; if((rc = _open_listenfd(port)) < 0) unix_error("Open server listen error"); return rc; } //建立监听函数,内部调用 int _open_listenfd(int port) { int listenfd; //服务器套接字描述符 int optval = 1; struct sockaddr_in serveraddr; if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) //返回-1表示出错 return -1; if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(int)) < 0) //忽略地址正在使用的错误 { close(listenfd); return -1; } bzero((char *)&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //接受来自主机的任何IP地址 serveraddr.sin_port = htons((unsigned short)port); //网络字节顺序 if(bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) //绑定 { close(listenfd); return -1; } if(listen(listenfd, MAX_LISTEN) < 0) //开始监听 { close(listenfd); return -1; } return listenfd; } #endif /* SERVER_H_ */上面代码中,用到了一些结构体及函数,这里给出一个简单的介绍。
/* <netdb.h> * DNS主机条目结构 * struct hostent{ * char *h_name; 官方名字 * char **h_aliases; 一组别名 * int h_addrtype; 地址类型 * int h_length; 地址字节长度 * char **h_addr_list; 一组IP地址 * }; */ /* <sys/socket.h> * 套接字地址结构 * struct sockaddr{ * unsigned short sa_family; * sa_data[14]; * }; * struct in_addr{ * unsigned int s_addr; 网络字节顺序,大端法 * }; * struct sockaddr_in{ 后缀_in表示internet * unsigned short sin_family; * unsigned short sin_port; 端口号 * struct in_addr sin_addr; IP地址 * unsigned char sin_zero[8]; 补齐 * }; */ /* * <string.h> * extern void bzero(void *s, int n) * extern void bcopy(const void *src, void *dest, int n) */