之前搭建服务器都是用的开源框架来实现,心中一直有不少的疑惑,操作系统到底是怎么实现服务器功能? 最近结合《详解TCP/IP》和《深入理解计算机系统》这两本书得以管中窥豹,突然间有种豁然开朗的感觉。
将我个人的理解以作笔记,都知道socket是网络通信的基石,用socket写了一个serverapp和clientapp。
其中客户端clientapp.c的代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
int open_clientfd(char *hostname,char *port);
int main(int argc, char * argv[]) {
int clientfd;
if (argc != 3) {
printf("must three params \n");
return 0;
}
char *hostname = argv[1];
char *port = argv[2];
clientfd = open_clientfd(hostname,port);
char str[64];
if (clientfd < 0) {
return 0;
}
read(clientfd,str,64);
printf("%s", str);
close(clientfd);
return 0;
}
int open_clientfd(char *hostname, char *port)
{
int clientfd, rc;
struct addrinfo hints, *listp, *p;
memset(&hints,0,sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags |= AI_ADDRCONFIG;
hints.ai_flags = AI_NUMERICSERV;
if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
printf("getaddrinfo: failed\n");
return -2;
}
for (p = listp; p ; p = p->ai_next) {
if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
continue;/*socket faied try the next*/
}
if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) {
break;//Success
}
if (close(clientfd) < 0) {
printf("open_lientfd: close failed\n");
return -1;
}
}
freeaddrinfo(listp);
if (!p) {
printf("All connects faied \n");
return -1;
} else {
return clientfd;
}
}
服务器端serverapp.c的代码如下
#include
#include
#include
#include
#include
#include
#include
#include
int open_listenfd(char *port);
int main(int argc, const char * argv[]) {
int listenfd, connfd = 0;
socklen_t clientlen;
listenfd = open_listenfd("8887");
int connd = accept(listenfd, (struct sockaddr*)&clientlen, &clientlen);
if (connfd < 1) {
printf("appect error");
}
char str[] = "qiansheng \n";
write(connd, str, sizeof(str));
close(connfd);
close(listenfd);
return 0;
}
int open_listenfd(char *port) {
struct addrinfo hints, *listp, *p;
int listenfd = 0, optval = 1;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; // 被动的 任何IP address
hints.ai_flags |= AI_NUMERICSERV; // use port number
getaddrinfo(NULL, port, &hints, &listp);
for (p = listp; p ; p = p->ai_next) {
if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
continue;
}
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(int));
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) {
break;//Success
}
close(listenfd);
}
freeaddrinfo(listp);
if (!p) {
return -1;
}
if (listen(listenfd, 30) < 0) {
close(listenfd);
return -1;
}
return listenfd;
}
在终端使用gcc命令分别执行
gcc -g serverapp.c -o serverapp
gcc -g clientapp.c -o clientapp
在serverapp和clientapp可执行文件后面加上参数就可以运行了
以本机电脑为例
./serverapp 8887 /**为了避免跟系统端口重复,选个端口号大一点的*/
./clientapp 127.0.0.1 8887
终端就会打印我们想要的hello word了 其中getaddrinfo函数是核心,可重入,适用于任何协议。
int getaddrinfo(const char *host,const char *service, const struct addrinfo *hints, struct addrinfo **result);//如果成功返回0 错误则为非零的错误代码
getaddrinfo返回result,result 指向一个addrinfo结构的链表。其中每个结构指向一个对应于host和service的套接字地址结构如下图。
在调用getaddrinfo之后,会遍历这个列表,依次尝试每个套接字地址,直到调用socket和connect成功,建立连接。为了避免内存泄漏,最后需要调用freeaddrinfo函数,释放该链表。如果getaddrinfo返回非零的错误代码,程序可以调用gai_streeror将该代码转成消息字符串。
虽然很简单,服务器也是以这做为基石。对于前端同学了解服务器端的运行还是有很大的帮助。
多说两句,serverapp运行在本地,clientapp在局域网任何一台电脑上运行clientapp
./clientapp 172.32.42.42 8887 / **serverapp 所在局域网的IP地址*/
可能你会想,为什么只是局域网不是所有外网?当然也是可以运行外网,可是我们的外网IP地址都被路由器给屏蔽了呀,具体可参见NAT协议
最后吐槽下:在看网络相关的书籍的时候,总是搞不懂为什么会有如此多的协议,比如说有了IP地址为什么还要搞个mac地址,知乎上有这个问题,很多人的回答跟我猜想的一样,网络这个东西经过那么多人的改动,在不停的妥协和兼容下,越来越庞大,也越来越累赘,当然他们也是伟大的先知。