各种结构体:
struct sockaddr{
unsigned short sa_family; /* 地址家族,AF_xxx */
char sa_data[14]; /*14字节协议地址*/
};
struct sockaddr_in {
short int sin_family; /* 通信类型 */
unsigned short int sin_port; /* 端口 */ 2字节
struct in_addr sin_addr; /* Internet 地址 */ 4字节
unsigned char sin_zero[8]; /* 与sockaddr结构的长度相同*/
//8字节 (这三个参数一共14字节,使之与sockaddr相等互用)
};
struct in_addr { //一般socket函数中用char*来指向改结构体
in_addr_t s_addr;
};
struct hostent {
char * h_name; /* official name of host */
char ** h_aliases; /* alias list */
short h_addrtype; /* host address type, AF_INET for TCP/IP*/
short h_length; /* length of address, 32bit for IPv4*/
char ** h_addr_list; /* list of addresses */
#define h_addr h_addr_list[0] /* address, for backward compat */
//h_addr 是指向的in_addr结构体的地址,通常*((struct in_addr *)h->h_addr) 来取IP地址
};
网络编程中的变量:
1、字符串类型的IP地址char *a: “192.168.1.1”
2、字符串类型的主机名地址char *name: “www.qq.com”
3、网络类型in_addr_t in:192.168.1.1
1、in_addr_t inet_addr(const char *cp); //将字符串形的点分十进制转换成网络类型
用法:
in_addr in;
in = inet_addr("192.168.1.1");
2、char *inet_ntoa(struct in_addr in); //将网络类型转换成字符串形的点分十进制
3、struct hostent* gethostbyname(const char *name);
用法:
struct hostent *ht;
ht = gethostbyname("www.QQ.com");
一、Berkeleysocket函数
第一部分是用于网络I/O的函数,如
accept、connect、closesocket/close、recv、recvfrom、select、send、sendto
第二部分是不涉及网络I/O、在本地端完成的函数,如
bind、getpeername、getsockname、getsocketopt、htonl、htons、inet_addr、inet_nton
ioctlsocket、listen、ntohl、ntohs、setsocketopt、shutdow、socket等
第三部分是检索有关域名、通信服务和协议等Internet信息的数据库函数,如
gethostbyaddr、gethostbyname、gethostname、getprotolbyname
getprotolbynumber、getserverbyname、getservbyport
关于阻塞和非阻塞,第二部分“不涉及网络I/O、本地端工作“的函数是非阻塞函数;网络I/O的函数是可阻塞函数,也就是它们可以阻塞执行,也可以不阻塞执行。这些函数都使用了一个socket,如果它们使用的socket是阻塞的,则这些函数是阻塞函数;如果它们使用的socket是非阻塞的,则这些函数是非阻塞函数。 默认创建的socket是阻塞的。
阻塞情况:
connect函数:找IP地址对应的主机途中会阻塞,如果找到IP地址对应的主机,并且对应端口在listen状态就会返回0(表示成功),否则返回-1(表示失败)。注意:失败后的sockfd必须关闭,不能再用connect来连接,需重新socket一个新sockfd。
accept函数:若已连接队列里取客户端套接口,如果已连接队列为空,则阻塞。
TCP中的read、write、send、recv等阻塞情况:
每个已经建立TCP连接的套接口拥有两个队列——“发送缓冲区”和“接收缓冲区”。
1、调用read和recv时,若缓冲区有数据就会返回,否则会阻塞直到有数据。
2、调用write和send时,当发送缓冲区空闲大小大于发送数据的大小则会直接返回(并非需要对端调用recv),否则阻塞直到空闲位置足够大小。
二、
1、bind函数
服务器可以同时bind指定的IP地址(因为一台服务器可能有多个网络借口或IP地址)和端口,意义是:监听指定IP和端口。
例如:在同一个机子里运行以下的服务器客户端
服务端bind指定IP:
addr.sin_addr.s_addr =inet_addr("192.168.1.103"); //内网IP号
addr.sin_port=htons(13);
bind(skfd,(sockaddr*)&addr,sizeof(sockaddr_in));
客户端通过回流地址访问服务器:
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
servaddr.sin_port = htons(13);
connect(sockfd, (sockaddr *) &servaddr, sizeof(servaddr));
运行结果:connect失败返回。因为就算客户端通过回流方式链接本机的服务端,由于服务端bind了192.168.1.103,只能通过192.168.1.103来访问服务器。
但可以bind定特殊宏INADDR_ANY(通配地址)表明任何服务器监听任意一个IP地址。
2、accept函数
int accept(int sockfd, void *addr, int *addrlen);
accept参数里的sockfd是监听sockfd,返回的是新的已连接sockfd,已连接sockfd和监听sockfd同一个端口但不同等(此时close掉监听sockfd是不影响已连接sockfd的读写)。
同时,sockfd是一个文件描述符,内有计数器。当父进程accept产生新的已连接sockfd时,fork一个子进程,子进程会复制父进程的sockfd并计数器+1。此时若父进程close掉sockfd,也不会影响子进程的对sockfd的读写,因为sockfd的计数器不为0,close并没真正关掉sockfd。