Webbench源码学习
源码下载
官网 http://home.tiscali.cz/~cz210552/webbench.html
源码运行
解压后可以发现代码文件只有socket.c和webbench.c
新建xcode工程,添加代码文件编译
报错:socket.c重定义
解决:删除webbench文件中的 #include “socket.c” 因为xcode会自动寻找。同时记得在webbench.c文件中新包含stdio.h和stdlib.h,因为现在不会自动包含socket.c下的头文件。
源码解析
socket.c
函数 int Socket(const char *host, int clientPort) 将主机名转化为ip地址后建立连接
Socket网络编程常用的结构体及函数小结
Socket套接字,本意是插座,在网络中用来描述计算机中不同程序与其他计算机程序的通信方式。
常用的套接字类型有3种:(1)流套接字(SOCK——STREAM):使用了面向连接的可靠的数据通信方式,即TCP协议;(2)数据报套接字(Raw Sockets):使用了不面向连接的数据传输方式,即UDP;(3)原始套接字(SOCK——RAW):没有经过处理的IP数据包,可以根据自己程序的要求进行封装。
套接字相关的数据类型:sockaddr和sockaddr_in 用来保存一个套接字的信息。
区别:sockaddr_in将ip地址与端口分开为不同的成员。可以相互转换。
struct sockaddr
{
unsigned short int sa_family; //指定通信地址类型,如果是TCP/IP通信,则值为AF_inet
char sa_data[14]; //最多用14个字符长度,用来保存IP地址和端口信息
};
struct sockaddr_in
{
unsigned short int sin_family; //指定通信地址类型
uint16_t sin_port; //套接字使用的端口号
struct in_addr sin_addr; //需要访问的IP地址
unsigned char sin_zero[8]; //未使用的字段,填充为0
};
struct in_addr
{
uint32_t s_addt;
};
用来保存主机信息的结构体:hostent
struct hostent
{
char *h_name;//正式的主机名称
char **h_aliases;//这个主机的别名
int h_addrtype;//主机名的类型
int h_length;//地址的长度
char **h_addr_list;//从域名服务器取得的主机地址
};
用来保存协议相关信息的结构体:protent
struct protoent
{
char *p_name;//协议的名称
char **p_aliases;//协议的别名
int p_proto;//协议的序号
};
用来保存服务相关信息的结构体:servent
struct servent
{
char *s_name;//这个服务的名称
char **s_aliases;//这个服务可能的别名
int s_port;//这个服务可能的端口
char *s_proto;//这个服务可能使用的协议
};
*以上结构体都存在一一对应的get函数
常用函数
创建套接字函数 int socket(int domain,int type,int protocol);
参数domain用于指定创建套接字所使用的协议族(可取AF_UNIX,AF_INET,AF_INTE6),参数type指定套接字的类型(可取SOCK_STREAM,SOCK_DGRAM,SOCK_RAW),参数protocol通常设置为0。
建立连接函数 int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
参数sockfd是一个由函数socket创建的套接字,参数serv_addr是一个地址结构,指定服务器的IP地址和端口号,参数addrlen为参数serv_addr的长度。
套接字与端口绑定函数 int bind(int sockfd,struct sockaddr *my_addr,socklen_t addrlen);
一般只有服务器端的程序调用,参数my_addr指定了sockfd将绑定到的本地地址,可以将参数my_addr的sin_addr设置为INADDR_ANY而不是某个确定IP地址就可以绑定到任何网络接口。
接收连接请求函数 int accept(int s,struct sockaddr *addr,socklen_t *addrlen);
参数s是由函数socket创建,经函数bind绑定到本地某一端口上,然后通过函数listen转化而来的监听套接字,参数addr用来保存发起连接请求的主机的地址和端口,参数addrlen是addr所指向的结构体的大小。
TCP套接字发送数据函数 ssize_t send(int s,const void *msg,size_t len,int flags);
函数只能对处于连接状态的套接字使用,参数s为已建立好连接的套接字描述符,即accept函数的返回值,参数msg指向存放待发送数据的缓冲区,参数len为待发送数据的长度,参数flags为控制选项,一般设置为0。
TCP套接字接收数据函数 ssize_t recv(int s,void *buf,size_t len,int flags);
函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收数据并保存到参数buf所指定的缓冲区,参数len则为缓冲区长度,参数flags为控制选项,一般设置为0。
UCP套接字发送数据函数 ssize_t sendto(int s,const void *msg,size_t len,int flags,const struct sockaddr *to,socklen_t tolen);
函数功能与函数send类似,但函数sendto不需要套接字处于连接状态,所以该函数通常用来发送UDP数据,同时因为是无连接的套接字,在使用sendto时需要指定数据的目的地址,参数msg指向待发送数据的缓冲区。参数len指定了待发送数据的长度,参数flags是控制选项,含义与send函数中的一致,参数to用于指定目的地址,目的地址的长度由tolen指定。
UDP套接字接收数据函数 ssize_t recvfrom(int s ,void *buf,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen);
与函数recv功能类似,只是函数recv只能用于面向连接的套接字,而函数recvfrom没有此限制,可以用于从无连接的套接字上接收数据。参数buf指向接收缓冲区,参数len指定了缓冲区的大小,参数flags是控制选项,含义与recv中的一致,如果参数from非空,且该套接字不是面向连接的,则函数recvfrom返回时,参数from中将保存数据的源地址,参数fromlen在调用recvfrom前为参数from的长度,调用recvfrom后将保存from的实际大小。
* 源码注释
int Socket(const char *host, int clientPort)
{
int sock; //套接字
unsigned long inaddr; //保存IP地址
struct sockaddr_in ad; //新建socket结构体
struct hostent *hp; //新建主机结构体
memset(&ad, 0, sizeof(ad)); //初始化
ad.sin_family = AF_INET; //通信地址类型ipv4
inaddr = inet_addr(host); //点分十进制的IP转换成一个长整数型数(u_long类型)
//host为ip地址时
if (inaddr != INADDR_NONE) // #define INADDR_NONE 0xffffffff 无效IP地址
memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); //保存到socket结构体
//host为域名时
else
{
hp = gethostbyname(host); //返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针
if (hp == NULL)
return -1; //调用失败
memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); //保存到socket结构体
}
ad.sin_port = htons(clientPort); //设置端口号
sock = socket(AF_INET, SOCK_STREAM, 0); //创建套接字 使用TCP协议
if (sock < 0) //socket返回的值是一个文件描述符,0,1,2分别表示标准输入、标准输出、标准错误。所以其他打开的文件描述符都会大于2,错误时就返回 -1
return sock;
if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) //建立连接 0 成功 SOCKET_ERROR 错误(<0)可用WSAGETLASTERROR函数取错误码
return -1;
return sock; //返回socket
}