/*
* Simple "Hello, World!" server
* Ivan Griffin (ivan.griffin@ul.ie)
*/
/* Hellwolf Misty translated */
#include /* */
#include /* exit() */
#include /* memset(), memcpy() */
#include /* uname() */
#include
#include/* socket(), bind(),
listen(), accept() */
#include
#include
#include
#include /* fork(), write(), close() */
/*
* constants
*/
const char MESSAGE[] = "Hello, World!\n";
const int BACK_LOG = 5;
/*
*程序需要一个命令行参数:需要绑定的端口号
*/
int main(int argc, char *argv[])
{
int serverSocket = 0,
on = 0,
port = 0,
status = 0,
childPid = 0;
struct hostent *hostPtr = NULL;
char hostname[80] = "";
struct sockaddr_in serverName = { 0 };
if (2 != argc)
{
fprintf(stderr, "Usage: %s \n",
argv[0]);
exit(1);
}
port = atoi(argv[1]);
/ *
*socket()系统调用,带有三个参数:
*1、参数domain指明通信域,如PF_UNIX(unix域),PF_INET(IPv4),
* PF_INET6(IPv6)等
*2、type指明通信类型,最常用的如SOCK_STREAM(面向连接可靠方式,
* 比如TCP)、SOCK_DGRAM(非面向连接的非可靠方式,比如UDP)等。
*3、参数protocol指定需要使用的协议。虽然可以对同一个协议
* 家族(protocol family)(或者说通信域(domain))指定不同的协议
* 参数,但是通常只有一个。对于TCP参数可指定为IPPROTO_TCP,对于
* UDP可以用IPPROTO_UDP。你不必显式制定这个参数,使用0则根据前
* 两个参数使用默认的协议。
*/
serverSocket = socket(PF_INET, SOCK_STREAM,
IPPROTO_TCP);
if (-1 == serverSocket)
{
perror("socket()");
exit(1);
}
/*
* 一旦套接口被建立,它的运作机制可以通过套接口选项(socket option)进行修改。
*/
/*
* SO_REUSEADDR选项的设置将套接口设置成重新使用旧的地址(IP地址加端口号)而不等待
* 注意:在Linux系统中,如果一个socket绑定了某个端口,该socket正常关闭或程序退出后,
* 在一段时间内该端口依然保持被绑定的状态,其他程序(或者重新启动 的原程序)无法绑定该端口。
*
* 下面的调用中:SOL_SOCKET代表对SOCKET层进行操作
*/
on = 1;
status = setsockopt(serverSocket, SOL_SOCKET,
SO_REUSEADDR,
(const char *) &on, sizeof(on));
if (-1 == status)
{
perror("setsockopt(...,SO_REUSEADDR,...)");
}
/* 当连接中断时,需要延迟关闭(linger)以保证所有数据都
* 被传输,所以需要打开SO_LINGER这个选项
* linger的结构在/usr/include/linux/socket.h中定义:
* struct linger
* {
* int l_onoff; /* Linger active */
* int l_linger; /* How long to linger */
* };
* 如果l_onoff为0,则延迟关闭特性就被取消。如果非零,则允许套接口延迟关闭。
* l_linger字段则指明延迟关闭的时间
*/
{
struct linger linger = { 0 };
linger.l_onoff = 1;
linger.l_linger = 30;
status = setsockopt(serverSocket,
SOL_SOCKET, SO_LINGER,
(const char *) &linger,
sizeof(linger));
if (-1 == status)
{
perror("setsockopt(...,SO_LINGER,...)");
}
}
/*
* find out who I am
*/
status = gethostname(hostname,
sizeof(hostname));
if (-1 == status)
{
perror("gethostname()");
exit(1);
}
hostPtr = gethostbyname(hostname);
if (NULL == hostPtr)
{
perror("gethostbyname()");
exit(1);
}
(void) memset(&serverName, 0,
sizeof(serverName));
(void) memcpy(&serverName.sin_addr,
hostPtr->h_addr,
hostPtr->h_length);
/*
*h_addr是h_addr_list[0]的同义词,
*h_addr_list是一组地址的数组
*长度为4(byte)代表一个IP地址的长度
*/
/*
* 为了使服务器绑定本机所有的IP地址,
* 上面一行代码需要用下面的代码代替
* serverName.sin_addr.s_addr=htonl(INADDR_ANY);
*/
serverName.sin_family = AF_INET;
/* htons:h(host byteorder,主机字节序)
* to n(network byteorder,网络字节序
* s(short类型)
*/
serverName.sin_port = htons(port);
/* 在一个地址(本例中的serverSocket)被建立后
* 它就应该被绑定到我们获得的套接口。
*/
status = bind(serverSocket,
(struct sockaddr *) &serverName,
sizeof(serverName));
if (-1 == status)
{
perror("bind()");
exit(1);
}
/* 现在套接口就可以被用来监听新的连接。
* BACK_LOG指定了未决连接监听队列(listen queue for pending connections)
* 的最大长度。当一个新的连接到达,而队列已满的话,客户就会得到连接拒绝错误。
* (这就是dos拒绝服务攻击的基础)。
*/
status = listen(serverSocket, BACK_LOG);
if (-1 == status)
{
perror("listen()");
exit(1);
}
/* 从这里开始,套接口就开始准备接受请求,并为他们服务。
* 本例子是用for循环来达到这个目的。一旦连接被接受(accpepted),
* 服务器可以通过指针获得客户的地址以便进行一些诸如记录客户登陆之类的
* 任务。
for (;;)
{
struct sockaddr_in clientName = { 0 };
int slaveSocket, clientLength =
sizeof(clientName);
(void) memset(&clientName, 0,
sizeof(clientName));
slaveSocket = accept(serverSocket,
(struct sockaddr *) &clientName,
&clientLength);
if (-1 == slaveSocket)
{
perror("accept()");
exit(1);
}
childPid = fork();
switch (childPid)
{
case -1: /* ERROR */
perror("fork()");
exit(1);
case 0: /* child process */
close(serverSocket);
if (-1 == getpeername(slaveSocket,
(struct sockaddr *) &clientName,
&clientLength))
{
perror("getpeername()");
}
else
{
printf("Connection request from %s\n",
inet_ntoa(clientName.sin_addr));
}
/*
* Server application specific code
* goes here, e.g. perform some
* action, respond to client etc.
*/
write(slaveSocket, MESSAGE,
strlen(MESSAGE));
/* 也可以使用带缓存的ANSI函数fprint,
* 只要你记得必要时用fflush刷新缓存
*/
close(slaveSocket);
exit(0);
default: /* parent process */
close(slaveSocket);/* 这是一个非常好的习惯
* 父进程关闭子进程的套接口描述符
* 正如上面的子进程关闭父进程的套接口描述符。
*/
}
}
return 0;
} |