UNP卷一chapter4 基本的套接字了解

以下知识点来均来自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,不太理解!

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80114561