以下知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。
由于刚开始接触套接字编程,主要是了解下图中各个套接字的功能和用法。
1、socket函数(指定期望的通信协议类型)
#include<sys/socket.h> int socket(int family, int type, int protocol);//若成功则为非负描述符,若出错则为-1
family:一般为AF_INET,AF_INET6(只列举目前常见的);
type:SOCK_STREAM、SOCK_DGRAM;
protocol:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP;
2、connect函数(TCP客户用connect函数来建立与tcp服务器的连接,即三次握手主要发生在此环节)
#include<sys/socket.h> int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen);//返回:若成功则为0,若出错为-1
需要注意一下,servaddr参数为套接字地址结构对象指针,必须首先初始化(服务器的IP地址和端口号)。
3、bind函数(将一个本地协议地址赋予一个套接字,即ip地址与端口号的组合)
int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen);//返回:若成功则为0,若出错则为-1
调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。
对IPv4用法:
struct sockaddr_in servaddr; servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//通配地址INADDR_ANY值一般为0,即告知内核去选择IP地址对IPv6用法:
struct sockaddr_in6 serv; serv.sin6_addr = in6addr_any;
4、listen函数(将sockfd由主动连接转为被动连接,仅由tcp服务器调用)
int listen(int sockfd, int backlog);//返回:若成功则为0,若出错则为-1
调用listen导致套接字从CLOSED状态转换到LISTEN状态,其第二个参数表示相应套接字排队最大连接个数。
5、accept函数(由tcp服务器调用,用于从已完成连接队列队头返回下一个已完成连接,如果已完成队列为空,进程投入睡眠)
#include<sys/socket.h> int accept(int sockfd, struct sockaddr* cliaddr, socklen_t *addrlen);//返回:若成功则为非负描述符,若出错则为-1
举例:
connfd = accept(listenfd, (SA*)&cliaddr, &len);//listenfd为监听套接字,connfd为已连接套接字
注意,已连接套接字在每次循环中关闭,监听套接字在服务器的父进程的整个有效期内保持开放。但在监听套接字在子进程中首先被关闭,但子进程执行完任务后,也关闭子进程中的已连接套接字。
6、fork和exec函数
#include<unistd.h> pid_t fork(void);//返回:在子进程中为0,在父进程中为子进程ID,若出错则为-1
针对fork函数,需要强调的是,fork函数返回两次,在父进程与子进程各返回值不同,特别注意。fork在父进程中返回子进程pid,是因为子进程可以有多个,需要明确知道所产生子进程的pid,而在子进程中返回0,是因为子进程有且只一个父进程,可以通过getppid获取父进程的pid。
#include<unistd.h> //以下函数均返回:若成功则不返回,若出错则返回-1 int execl(const char* pathname, const char* arg0, .../*(char*) 0*/); int execv(const char *pathname, char *const *argv[]); int execle(const char* pathname, const char* arg0, .../*(char*) 0,char* const evp[]*/); int execve(const char* pathname, char* const argv[], char *const evnp[]); int execlp(const char* filename, const char *arg0, .../*(char*) 0*/); int execvp(const char * filename, char * const argv[]);
存放在硬盘上的可执行程序文件能够被Unix执行的唯一方法是:由一个现有进程调用 六个exec函数之一。exec把当前进程映像替换成新的程序文件,而且该新程序通常从main函数开始执行。
六个函数的区别:a、待执行的程序文件是文件名(filename)还是路径名(pathname)指定;b、新程序的参数是一一列出还是由一个指针数组来引用;c、把调用进程的环境传递给新程序还是给新程序指定新的环境。
7、关发服务器的一个典例
#include<unp.h> void main(){ pid_t pid; int listenfd, connfd; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port=htons(SERV_PORT); Bind(listenfd, (SA*)&servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); for (;;) { clilen = sizeof(cliaddr); confd = accept(listenfd, (SA*)&cliaddr, &clilen); if ((pid = fork()) == 0) {//childen process Close(listenfd);//child closes listening socket doit(connfd);//process the request Close(connfd); exit(0); } Close(connfd);//father process close connection socket } }
描述符引用计数有点类似c11中智能指针中的引用计数器,只有当所引用的对象的计数器为0,才进行4次挥手断开连接。
8、getsockname和getpeername函数
#include<sys/socket.h> //以下两函数,若成功返回0,若出错则为-1 //最后一个参数是值-结果参数 int getsockname(int sockfd, struct sockaddr* localaddr, socklen_t* addrlen); //使用getsockname的场景:返回内核赋予该连接的本地ip地址和本地端口号。 int getpeername(int sockfd, struct sockaddr* peeraddr, socklen_t* addrlen); //具体见书上P95,不太理解!