大周六的无聊的很,不如来继续看这一大段开源代码,这一次我们按照Github上面那位大佬推荐的步骤来看推荐的步骤来看,先看start_up函数。 说多无益,先上该段代码:
int startup(u_short *port)
{
int httpd = 0;
int on = 1;
struct sockaddr_in name;
httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1)
error_die("socket");
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
if ((setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0)
{
error_die("setsockopt failed");
}
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
if (*port == 0) /* if dynamically allocating a port */
{
socklen_t namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
error_die("getsockname");
*port = ntohs(name.sin_port);
}
if (listen(httpd, 5) < 0)
error_die("listen");
return(httpd);
}
这个函数的注释。。。。其实我也看不懂,建议谷歌翻译一下。大概意思是说哈,开启一个线程在参数中的端口不为0的情况下监听一个端口,如果端口为0的话,那么就随机分配一个接口来使用。 接下来就是socket函数,这个函数是在sys/types.h头文件中定义的一个函数,这个函数的参数如下:
int socket(int domain, int type, int protocol);
这个函数的作用是用来建立一个协议族为domain,协议类型为type,协议编号为protocol的套接字描述符,如果函数调用成功的话,会返回标示这个套接字的文件描述符,如果失败就会返回-1. domain这个参数是用来设置网路通信的域,socket函数根据这个参数来选择通信协议的族,通信协议在头文件sys/socket.h中定义。 type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字),SOCK_DGRAM(数据包套接字)等。
SOCK_STREAM//tcp连接,提供序列化的可靠的双向连接的字节流
SOCK_DGRAM//支持udp连接
SOCK_SEQPACKET//序列化包,提供一个序列化的,可靠的,双向的基本连接的数据连接通道,数据长度定常。
SOCK_RAW//提供原始网络协议访问
SOCK-RDM//提供可靠的数据报文,不过可能数据会有乱序
SOCK_PACKET//这是一个专有类型,不过能够在通用程序中使用
接下来的一句就是通过httpd的值来判断,如果是-1的话那就认为是套接字建立不成功,然后程序接下来没得玩了,这个还真没有什么好说的
/**********************************************************************/
/* Print out an error message with perror() (for system errors; based
* on value of errno, which indicates system call errors) and exit the
* program indicating an error. */
/**********************************************************************/
void error_die(const char *sc)
{
perror(sc);
exit(1);
}
接下来就是memset函数,这个函数定义如下:
// 将s中的n个字节的内容全部挨个替换成ch,返回s
void *memset(void *s, int ch, size_t n);
我用过一个带s版本和不带s的版本,这两个版本的函数平时都是用来将动态分配的内存进行清空操作和分配操作的,但是这个函数有意思的一点时,它赋值是逐字节逐字节进行赋值的,比如:
char str[5];
我们想给这个数组全部赋值为1,那么就可以这么写
memset(str,1,5)
这样子就是”00000“,但是有意思的是,如果给其他数据类型的数组进行赋值时,比如我想通过给int类型,长度为5的数组进行赋值
memset(num,1,sizeof(int)*5)
按照常理来说,我们认为num数组每个元素的值都是0x1,但是实际上来说每个元素的值其实是0x000000001,000000001,000000001,000000001,说白了就是每个字节都赋值了0x1这个值,这里要千万注意,要不然会出现奇怪的bug,毕竟解决bug耗费的时间还是很巨大的。 不过看到这里我突然想起了一个很容易出错的点,也是我曾经在哪本书,好像是计算机组成原理上讲的一个点
struct{
char a;
int b;
}
如果给他套上一个sizeof,会return一个什么值呢? 其实这个问题的等效问题就是有多种数据类型的结构体在分配内存时,是分配多少,例如:
struct A{
char a;
int b;
short c;
};
struct B{
char a;
short b;
int c;
};
这两个结构体的内存占用到底一样不一样; 在结构体中有两个规则 1.每个结构体成员的起始地址为该成员大小的整数倍,即int型成员的起始地址只能为0、4、8等; 2.结构体的大小为其中最大成员大小的整数倍; 所以我们可以分析一下结构体A,在32位系统中,char类型占用1个字节,下一个就是int类型(4个字节),根据规则1,int类型的起始地址只能为4n(n=0,1,2,…),所以需要在char类型和int类型之间填充3个字节,这就是8个字节,然后接下来是short(占用2个字节),这个时候就需要考虑规则2,所以结构体A需要占用12个字节,一共空了5个字节; 接下来分析结构体B,同样在32位系统中,char类型占用1个字节,这没什么好说的,下一个short的话,根据规则1,short类型的起始地址只能为2n(n=0,1,2,…),这个时候就需要在short和char之间填充1个字节,现在算下来就是4个字节,然后接下来是int,4个字节,这样子算下来B占用8个字节; 这只是默认情况下的结构体占用情况,如果想节省内存的话,可以使用宏来紧凑数据。