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