第11章 网络编程
1.
1.1为什么要分层?
减少协议设计的复杂性。
1.2TCP/IP各层功能
2.套接字编程
通常Linux下的网络编程就是指套接字编程.
2.1 套接字地址结构
struct sockaddr
//linux/socket.h
struct sockaddr{
unsigned short sa_family; //套接字的协议族类型,AF_XXX
char sa_data[14];//14字节,存储具体的协议地址
}
//长度16字节;一般在编程中不对该结构体进行操作,而是使用1个与它等价的数据结构:sockaddr_in
每种协议族都有自己的协议地址格式,TCP/IP协议族的地址格式为结构体struct sockaddr_in
/* netinet/in.h */
struct sockaddr_in{
unsigned short sin_family; //地址类型,对于TCP/IP:该值为AF_INET
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //32位IP地址
unsigned char sin_zero[8];//填充字节,一般赋值为0
}
//长度也为16字节
struct struct in_addr{
unsigned long s_addr;
}
通常在编写基于TCP/IP协议的网络程序时,使用结构体sockaddr_in来设置地址,然后强制类型转换成sockaddr类型.
设置地址信息的示例
struct sockaddr_in struct sockaddr_in sock;
sock.sin_family = AF_INET;
sock.sin_port = htons(80); //设置端口号
sock.sin_addr.s_addr = inet_addr("202.205.3.195");
memset(sock.sin_zero,0,sizeof(sock.sin_zero));
memset(void *s,int c,size_t n); //它将s指向的内存的前n个字节赋值为c的值
2.2 创建套接字
用来socket函数创建1个套接字,函数原型
#include #include
#include
int socket(int domain,int type,int protocol);
参数domain指定所使用的协议族,在linux/socket.h中定义. 常用的有
- AF_UNIX : 创建只在本机内进行通信的套接字
- AF_INET : 使用IPv4 TCP/IP协议
- AF_INET6 : 使用IPv6 TCP/IP协议
参数type指定套接字的类型,可以取如下值:
- SOCK_STREAM : 创建TCP流套接字
- SOCK_DGRAM : 创建UDP数据报套接字
- SOCK_RAW : 创建原始套接字
参数protocol通常设置为0. 当创建原始套接字时,需要设置该参数.
执行成功返回1个新创建的套接字;错误返回-1,错误代码存入errno中.
创建1个TCP套接字:
int sock_fd;
sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd < 0){
perror("socket error");
exit(1);
}
创建1个UDP套接字:
sock_fd = socket(AF_INET,SOCK_DGRAM,sock_fd = socket(AF_INET,SOCK_DGRAM,0);
2.3 建立连接
函数connect用来在一个指定的套接字上创建1个连接,函数原型
#include #include
#include
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
通常对于TCP套接字只能调用一次connect函数,而对于UDP套接字则可以调用多次以改变与目的地址的绑定。
将参数serv_addr中的sa_family设置为AF_UNSPEC可以取消绑定.
执行成功返回0;错误返回-1,错误代码存入errno中.
常见用法:
struct sockaddr_in struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(struct sockaddr_in));//将serv_addr各个字段清0
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(80); //设置端口号
//inet_aton将1个字符串转换成网络地址赋值给第二个参数
if(inet_aton("202.205.3.195",&serv_addr.sin_addr) < 0){
perror("inet_aton");
exit(1);
}
//使用sock_fd套接字连接到serv_addr指定的目的地址上,假定sock_fd已经定义
if(connect(sock_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) < 0){
perror("connect");
exit(1);
}
2.4 绑定套接字
函数bind用来将1个套机子和某个端口绑定在一起,函数原型
#include #include
#include
int bind(int sockfd,struct sockaddr *my_addr,socklen_t addrlen);
socket函数只是创建了1个套接字,没有指定工作在哪个端口. 服务端IP和端口号一般是固定的,该函数一般只有服务端的程序调用。
参数my_addr指定了sockfd将绑定到的本地地址.
执行成功返回0;错误返回-1,错误代码存入errno中.
常见用法:
struct sockaddr_in struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(struct sockaddr_in));//将serv_addr各个字段清0
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(80); //设置端口号
serv_addr.sin_addr.s_addr = hton1(INADDR_ANY);
if(bind(sock_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) < 0){
perror("bind");
exit(1);
}
2.5 在套接字上监听
函数listen把套接字转化为被动监听
#include #include
int listen(int sock_fd,int backlog);
函数socket创建的套接字是主动套接字,可以用来主动请求连接到某个服务器(通过函数connect).
但是作为服务端的程序,通常在某个端口监听等待来自客户端的连接请求.
在服务器端,一般是 socket -> bind -> listen
转化为监听套接字.
一般多个客户端连接到1个服务器。服务器设置一个连接队列,记录已建立的连接,参数backlog指定了该连接队列的最大长度。超过之后的连接请求将被拒绝.
执行成功返回0;错误返回-1,错误代码存入errno中.
常见用法:
##define LISTEN_NUM 12
...
if(listen(sock_fd,LISTEN_NUM) < 0){
perror("listen");
exit(1);
}
2.6 接受连接
函数accept用来接受一个连接请求,原型
#include #include
#include
int accept(int sock_fd,struct sockaddr *addr,socklen_t *addrlen);
参数sock_fd是监听套接字
参数addr用来保存发起连接请求的主机的地址和端口
参数addrlen是addr所指向的结构体的大小
执行成功返回1个新的代表1个新的套接字描述符,是客户端的套接字,;错误返回-1,错误代码存入errno中.
只能对面向连接的套接字使用accept函数. 新的套接字描述符类似打开文件时返回的文件描述符,进程可以利用它与客户端交换数据,参数sock_fd所指定的套接字继续等待客户端的连接请求.
accept()可能会被阻塞.
2.7 TCP套接字的数据传输
1.发送数据
函数send用来在TCP套接字上发送数据,原型
#include #include
#include
ssize_t send(int sock_fd, const void *buffer, size_t length, int flags);
- 参数sock_fd为已建立好连接的,即accpet函数的返回值
- buffer指向存放待发送数据的缓冲区
- length为待发送数据的长度
- 参数flags略
执行成功(只是说明数据写入套接字的缓冲区,不表示到达目的地)返回实际发送数据的字节数;错误返回-1,错误代码存入errno中.
套接字为阻塞方式下,常见用法:
##define BUFFERSIZE 1500
char send_buf[BUFFERSIZE];
...
if(send(conn_fd,send_buf,len,0) < 0){
perror("send");
exit(1);
}
2.接收数据
函数recv用来在TCP套接字上发送数据,原型
#include #include
#include
ssize_t send(int sock_fd, void *buffer, size_t length, int flags);
函数recv从参数sock_fd所指定的套机子描述符(必须是面向连接的)上接收数据并保存到参数buffer所指定的缓冲区,参数len则为缓冲区长度.
flags为控制选项,一般设置为0
执行成功返回接收到的数据的字节数;错误返回-1,错误代码存入errno中.
套接字为阻塞方式下,常见用法:
char recv_buf[BUFFERSIZE];
...
if(recv(coon_fd,recv_buf,sizeof(recv_buf),0) < 0){
perror("recv");
exit(1);
}
2.8 UDP套接字的数据传输
2.9 关闭套接字
1. 函数close
用来关闭一个套接字描述符,与关闭文件描述符是类似的
#include #include
int close(int fd)
执行成功返回0;错误返回-1,错误代码存入errno中.
2. 函数shutdown
也用于关闭一个套接字描述符,比close功能强大,允许单向关闭或全部禁止
#include #include
int shutdown(int sock_d,int howto)
参数howto指定了关闭的方式
- SHUT_RD : 将连接上的读通道关闭,此后进程将不能再接收任何数据,接收缓冲区未被读取的数据将被丢弃,但任然可以在该套接字上发送数据
- SHUT_WR : 将连接上的写通道关闭,此后进程将不能再发送任何数据,发送缓冲区未被读取的数据将被丢弃,但任然可以在该套接字上接收数据
- SHUT_RDWR : 读、写通道都将关闭
执行成功返回0;错误返回-1,错误代码存入errno中.
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!