主要摘自《深入理解计算机系统》一书,略作整理,加了些备注。可以简单了解一下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)
- */