以前小有接触,正好这学期选了一门类似的课,重新整理下。
首先是几个常用的网络基本配置文件:
/etc/hosts 主机名解析 /etc/services 不同服务所使用的端口定义 /etc/netmasks 网络掩码
然后是地址结构netinet/in.h
每个协议族都定义自己的套接口地址结构,这些结构名字以sockaddr_开头,并以每个协议族对应的唯一后缀结束。地址结构中的某些成员按照网络字节序进行维护。由于历史原因,sockaddr_in中的in_addr是一个结构体,只有一个unsigned long成员s_addr存储其对应形式的IPv4地址,典型赋值:
in_addr addr; addr.s_addr = 0x0100007f; //127.0.0.1的32位长整形网络字节序 addr.s_addr = inet_addr("127.0.0.1"); //更通常的做法
字节序:
注意网络传送使用的大端字节序,发送时总是从低字节开始。
而对于ASCII编码的字符串,首字符总是在内存的低字节。而utf的编码就与大小端有关了。
把套接口地址结构传递给套接口函数总是通过指针的方式来传递。结构的长度也作为参数来传递,但其传递的方式取决于结构的传递方向:
1. 从进程到内核传递套接口地址,三个典型函数:bind,connect,sendto。其长度参数为整型,确切告知从进程到内核要拷贝多少数据。
2.从内核到进程传递套接口地址,四个典型函数:accept,recvfrom,getsockname,getpeername。它们传递的是指向表示结构大小的整数的指针。
各基本函数:
socket: 创建一个套接字,并为套接字数据结构分配存储空间。protocol字段为0时,系统自动根据协议族和通信类型为其选择相应值。
int socket(int domain, int type, int protocol);
bind:绑定套接字到端口,套接字地址里IP地址字段指定为INADDR_ANY时(在netinet/in.h中定义),内核自动分配本机IP
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
listen:为申请进入的连接建立输入数据队列,将到达本地的客户服务请求保存在此队列上(包括已完成和未完成两个队列),直到程序处理它们。listen函数只用于TCP服务器。backlog的上限由<sys/socket.h>中SOMAXCONN指定。
int listen(int sockfd, int backlog);
connect和accept分别是发起连接和接受连接请求:
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len); int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
tcp流方式的发送和接收函数:
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags); //flags: 传输标志位,为0时是常规操作,如同write()函数 ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags); //flags: 传输标志位,为0时是常规操作,如同read()函数,当对方关闭连接时,返回的接收数据长度为0,通常以此判断对方是否关闭。
udp包方式的发送和接收函数:
ssize_t sendto(int s, void *msg, size_t len, int flags, const struct sockaddr *to, int tolen); ssize_t recvfrom(int s, void *buf, size_t, int flag, struct sockaddr *from, int *fromlen);
flag和tcp方式类似
以上函数均是不成功返回-1,并将errno置为相应的错误号。
errno - number of last error。errno 记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定义。可以通过:
#include<string.h> char *strerror(int errnum);该函数把错误码,转化成可理解的字符串。
也可直接使用下面这个函数输出出错信息:
void perror ( const char * str );
关闭套接字close, <unistd.h>
在<string.h>中,定义和名字以str开头的函数处理的是以空字符结束的C字符串。有一组以mem开头的内存操作函数在网络编程里比较常用:
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes); int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
字节序转换函数:
#include<netinet/in.h > u_long htonl(u_long hostlong); u_long ntohl(u_long netlong); u_short ntohs(u_short netshort); u_short htons(u_short hostshort);
IP地址转换函数:(a:ascii通常指点分十进制形式,n:network或numeric指整数形式)
#include<arpa/inet.h>
int inet_aton(const char*strptr, struct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);
in_addr_t inet_addr(const char *strptr);//把点分十进制的形式转化为网络中传输的32整形数据,如192.168.1.3 -> 50440384(3*256^3+1*256^2+168*256+192)
注意:255.255.255.255(有限广播地址)不能用inet_addr处理,因为处理后是32个1,与该函数出错时返回的常值INADDR_NONE相同。套接字地址信息函数:
int getsockname(int s, struct sockaddr *name, socklen_t *namelen); int getpeername(int s, struct sockaddr *name, socklen_t *namelen);
通常我们知道本地和远程的套接字地址信息,不必专门调用函数来获得,但是以下情况例外:
1、TCP客户不调用bind而直接调用connect,此时,应调用getsockname才能获得由系统自动分配给该连接的IP地址和端口号
2、调用bind,且端口参数设为0(让内核选择相应的端口),则调用getsockname获得本地端口
3、TCP服务器调用bind函数,且IP地址参数为INADDR_ANY,则调用getsockname获得由内核分配的本地IP地址
4、在子进程中,由于调用accept进程通过执行exec函数而丢失了原内存的数据,则调用getpeername函数获得远程的套接字地址信息