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;
}

浙公网安备 33010602011771号