CSAPP读书笔记11-02

CSAPP读书笔记11-02

11.3.3

客户端与服务器通过点对点的交互数据流进行通信,这是一个全双工的过程。这个过程中数据包的顺序是在客户端和服务器两者之间是相同的。套接字通过地址:端口来表示两个通信的主机。端口是16比特,也就是2个字节(065525)。其中01023是标准端口, 0~255主要分配给了常用的一些知名软件, 255~2013一般被操作系统占用。常见的有:

  • 网络服务:80、8080
  • 邮件服务: 25
  • FTP服务: 21

在Linux或者Unix中可以通过查看/etc/services文件来查看相关端口内容。套接字通信通过地址:端口进行一一配对。一般情况下,服务端的端口是固定的、已知的,而客户端则由操作系统在进程运行时分配。

11.4 套接字接口

套接字接口是与操作系统I/O相关连的函数集合,用来提供网络服务。目前所有操作系统都提供相关接口。

11.4.1 套接字地址结构体

从操作系统的角度来看,套接字也是文件。Unix以及类Unix中将一切都视为文件,套接字当然也不能例外。

/*头文件*/	sockaddr: socketbits.h (included by socket.h), sockaddr_in: netinet/in.h
/* Generic socket address structure (for connect, bind, and accept) */
struct sockaddr {
	unsigned short sa_family;		/* Protocol family,一般为AF_INET */
	char sa_data[14]; 				/* Address data. */
};

/* Internet-style socket address structure */
struct sockaddr_in {
	unsigned short sin_family;	/*Address family (一般为AF_INET,2个字节) */
	unsigned short sin_port;	/*Port number in network byte order 2个字节*/
	struct in_addr sin_addr;	/*IP address in network byte order 4个字节*/
	unsigned char sin_zero[8];	/*Pad to sizeof(struct sockaddr) 8个字节*/
};

11.4.2 socket()函数

socket()用来创建套接字描述符,和open()功能有点类似。这时创建的套接字描述符不具备读写权限。

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
						Returns: nonnegative descriptor if OK, −1 on error

11.4.3 connect()函数

客户端通过connect函数与服务器确立联系。

#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
													Returns: 0 if OK, −1 on error

serv_addr代表服务器地址数据结构,里面保护了协议类型、端口、地址等。如果连接成功,这时套接字描述符可以具有读写权限。

11.4.4 open_clientfd()函数

该函数是CSAPP作者将socket()和connect()进行封装的函数。实际项目中引用的场景应该不会很多,可以看看作者的封装思想,然后在项目中根据实际需求自己进行封装。

#include "csapp.h"
int open_clientfd(char *hostname, int port);
				Returns: descriptor if OK, −1 on Unix error, −2 on DNS error

调用open_clientfd()返回一个套接字描述符,可以直接进行I/O操作。

11.4.5 bind()函数

剩下的bind()、accept()和listen()都是用来服务端连接客户端的API。

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
													Returns: 0 if OK, −1 on error

bind()函数告诉内核根据服务器地址连接到服务器的套接字。

11.4.6 listen()函数

客户端程序是主动连接,服务器端程序是被动连接。内核在创建套接字时默认创建的是主动连接方式的套接字。为了区分客户端和服务器,使用listen()函数去通知内核程序是服务器,而不是客户端。

#include <sys/socket.h>
int listen(int sockfd, int backlog);(backlog等待连接队列的数量)
							Returns: 0 if OK, −1 on error

11.4.7 open_listenfd()函数

作者封装的函数。用来创建监听描述符。

#include "csapp.h"
int open_listenfd(int port);
								Returns: descriptor if OK, −1 on Unix error

作者在实现代码中使用了setsockopt()函数用来解决重启程序后,地址被占用的问题。在监听地址中使用INADDR_ANY宏,代表可以接受任意网络地址客户端发起的连接。还须注意的是在给端口以及地址赋值时,使用了htons()和htonl()函数将主机字节序转化为网络字节序。

11.4.8 accept()函数

服务器使用accept()函数来等待客户端的连接。

#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
						Returns: nonnegative connected descriptor if OK, −1 on error

例子:

CSAPP中例子较为复杂,其中一些东西是在前面单元封装的函数,不太好理解。刚好手头有《Unix网络编程》,其中的例子结构较为清晰。

客户端:
/***************************************************************************
* FileName: client.c
* Desc: 
* Author: Zhang Wenwei
* Email: 1026367056@qq.com
* HomePage: http://www.cnblogs.com/ti1023/
* Version: 0.0.1
* LastChange: 2016-09-25 09:43:50
* History:
****************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<unistd.h>

#define	BUFFSIZE	1024

int main(int argc, char *argv[])
{
	int			sockfd, n;
	char			buf[BUFFSIZE];
	struct	sockaddr_in 	sevaddr;

	if(argc != 2) {
		printf("usage: %s <IP address>\n", argv[0]);
		return -1;
	}
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		printf("socket error, %s\n", strerror(errno));
		return -1;
	}
	bzero(&sevaddr, sizeof(sevaddr));
	sevaddr.sin_family = AF_INET;
	sevaddr.sin_port = htons(3838);
	if(inet_aton(argv[1], &sevaddr.sin_addr) <= 0) {
		printf("inet_aton for %s, %s", argv[1], strerror(errno));	
		return -1;
	}	
	if(connect(sockfd, (struct sockaddr *)&sevaddr, sizeof(sevaddr)) < 0) {
		printf("connect error, %s\n", strerror(errno));	
		return -1;
	}
	while((n = read(sockfd, buf, BUFFSIZE)) > 0) {
		buf[n] = 0;	
		if(fputs(buf, stdout) == EOF) {
			printf("fputs error, %s\n", strerror(errno));	
			return -1;
		}
	}
	if(n < 0 ) {
		printf("read error, %s\n", strerror(errno));	
		return -1;
	}
	close(sockfd);

	return 0;
}

服务端:

/******************************************************************************
* FileName: server.c
* Desc: 
* Author: Zhang Wenwei
* Email: 1026367056@qq.com
* HomePage: http://www.cnblogs.com/ti1023/
* Version: 0.0.1
* LastChange: 2016-09-25 14:15:23
* History:
******************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<time.h>
#include<unistd.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define	MAXLINE		1024

int main(int argc, char *argv[])
{
	int			listenfd, connfd;
	int			ret;
	char			buf[MAXLINE];
	time_t			ticks;
	struct sockaddr_in	servaddr;

	if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		printf("socket error,%s\n", strerror(errno));
		return -1;	
	}
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(3838);		
	/* 服务器要响应任意IP地址客户端发起的连接请求,故要使用NADDR_ANY */
	servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
	if((ret = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0) {
		printf("bind error, %s\n", strerror(errno));	
		return -1;
	}
	if(( ret = listen(listenfd, 4)) < 0) {
		printf("listen error, %s\n", strerror(errno));	
		return -1;
	}
	for( ; ;) {
		if((connfd = accept(listenfd, (struct sockaddr *) NULL, NULL)) < 0) {
			printf("connect error, %s\n", strerror(errno));	
			return -1;
		}	
		ticks = time(NULL);
		snprintf(buf, sizeof(buf), "%.24s\r\n", ctime(&ticks));
		if((ret = write(connfd, buf, strlen(buf))) < 0) {
			printf("write error, %s\n", strerror(errno));	
			return -1;
		}
		close(connfd);
	}

	close(listenfd);	

	return 0;
}
posted @ 2016-09-25 14:38  寒雪安东侯  阅读(224)  评论(0)    收藏  举报