开启网络编程新篇章
hello,大家好,我是雨墨,小老弟我正在学习网络编程,想着既然都写了笔记,那么就分享出来吧,如果笔记中有什么问题,欢迎大家斧正!如果对你有用,那就点个赞再走吧~
理解网络编程和套接字
linux 头文件 #include <sys/socket.h>
基于linux平台的实现
服务器端
1. 调用socket函数创建套接字 // 建立电话线
int socket(int domain,int type ,int protocol);
int serv_sock(PF_INET, SOCK_STREAM, 0);
2. 调用bind函数分配IP地址和端口号 // 给你一个手机号
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
if (bind(serv_sock, (struct sockaddr)&serv_addr, sizoef serv_addr) == -1)
...
3. 调用listen函数转化为可接收请求状态 // 在电话机前等
int listen(int sockfd, int backlog); // backlog 表示消息队列的长度,自己设定
if (listen(serv_sock, 5) == -1)
...
4. 调用accept函数受理连接请求 // 接电话
int accept(int sockfd, struct sockaddr *addr , socklen_t *addrlen); // 创建 client sockfd
socklen_t clnt_addr_sz = sizeof clnt_addr;
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_sz)
客户端
1、调用 socket 函数创建套接字
int socket(int domain,int type ,int protocol);
int sock = socket(PF_INET, SOCK_SRTREAM, 0);
2、调用 connect 函数
int connect(int socketfd, struct sockadd* serv_addr, socklen_t addrlen);
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof serv_addr) == -1)
...
Linux 不区分文件和套接字,Linux 下万物皆是文件。
打开文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
open(const char *path , int flag);// path为文件地址, flag为文件开始模式,可能有多个,由|连接
例如 int fd = open("data.txt",O_CREAT|O_WRONLY|O_TRUNC);
O_CREAT 必要时创建文件
O_TRUNC 删除全部现有数据
O_APPEND 维持现有数据,保存到后面
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
关闭文件
#include <unistd.h>
int close(int fd);// fd为文件描述符
将数据写入文件
#include <unistd.h>
ssize_t write(int fd,const void * buf ,size_t nbytes)
size_t为无符号整形(unsigned int)的别名, ssize_t是signed int 类型
其中可以向数字写数据,例如 write(sock, (char*)&str_len, 4); // 解释:str_len是int,向str_len中写4个Byte的int,然后转为char
通常是预先告知对方要发的字符串的大小为多少
读取文件中数据
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t nbytes);
// fd 文件描述符 ,buf 保存接收数据缓冲地址值 nbytes 接收数据最大字节数
也可以向数字读数据,例如 read(sock, (char*)&str_len, 4) // 解释:读入四个字节存入str_Len中
套接字类型与协议设置
int socket(int domain, int type ,int protocol)
domain : 套接字中使用的协议族信息,本书采用 ipv4 ,所以使用 PF_INET
type: 套接字数据传输类型信息,按照面向连接传输分为 SOCK_STREAM ,按照面向数据从传输分为 SOCK_DGRAM
protocol: 计算机间通信使用的协议信息,由于面向连接传输且在 ipv4 中的是 IPPROTO_TCP ,所以根据前面两个信息可以推出它来,可以直接写 0 。同理 IPPROTO_UDP 也可直接用 0 表示。
协议族 : 协议分类信息,本书主要讨论 IPv4 ,所以使用的一般是 PF_INET
PF_INET IPv4互联网协议族
PF_INET6 IPv6
PF_LOCOL 本地通信的UNIX协议族
PF_PACKET 底层套接字的协议族
PF_IPX IPX Novell协议族
套接字类型(type):套接字的数据传输方式
-
面向连接的套接字(SOCK_STREAM)
特征:可靠,按序基于字节的面向连接(一对一)的数据传输方式的套接字 ,想象为传送带上传送东西,socket 相当于站在两个车间的工人。
-
面向消息的的套接字(SOCK_DGRAM)
特征: 不可靠,不按序,以数据的高速传输为目的的套接字,可以想象成骑着摩托车送快递的小哥,快递送的很快,但是容易坏,并且每次只能送那么多。
具体指定协议信息(protocol)
为啥需要第三个参数: 同一协议族中存在多个数据传输方式相同的协议
TCP 套接字(IPPROTO_TCP), write 函数调用次数可以和不同于 read 函数调用次数,由于在 ipv4 协议族中有且只有那个 SOCK_STREAM 的只有 IPPROTO_TCP,所以第三个参数可以直接写 0 。
地址族与数据序列
分配给套接字的IP地址与端口号
IP是为收发网络数据而分配给计算机的值,端口号是为区分程序中创建的套接字而分配给套接字的序号
IPv4: 4 字节地址族 IPv6 : 16 字节地址族
IPv4标准的 4 字节IP地址分为网络地址和主机地址,且根据网络ID和主机ID所占字节的不同,分为A(0-127),B(128-191),C(192-223),D,E
主机传输数据是先根据网络ID发送到相应路由器或交换机然后在根据主机ID向目标主机传递数据
端口号是在同一操作系统内区分不同套接字而设置的。不能将同一端口号分给不同套接字,但是 TCP 和 UDP 不会共用端口号,所以允许UDP和TCP使用同一端口号
地址信息的表示
表示 IPV4 地址的结构体
struct sockaddr_in
{
sa_family_t sin_family; // 地址族(Address Family)
uint16_t sin_port; // 16 位 TCP/UDP 端口号,以网络字节序保存
struct in_addr sin_addr; // 32位 IP 地址
char sin_zero[8]; // 不使用,必须填充为0,使sockaddr_in和sockadd结构体保持一致
};
该结构体中提到的另一个结构体 in_addr 定义如下,它用来存放 32 位IP地址
struct in_addr
{
in_addr_t s_addr; //32位IPV4地址
}
数据类型名称 | 数据类型说明 | 声明的头文件 |
---|---|---|
int 8_t | signed 8-bit int | sys/types.h |
uint8_t | unsigned 8-bit int (unsigned char) | sys/types.h |
int16_t | signed 16-bit int | sys/types.h |
uint16_t | unsigned 16-bit int (unsigned short) | sys/types.h |
int32_t | signed 32-bit int | sys/types.h |
uint32_t | unsigned 32-bit int (unsigned long) | sys/types.h |
sa_family_t | 地址族(address family) | sys/socket.h |
socklen_t | 长度(length of struct) | sys/socket.h |
in_addr_t | IP地址,声明为 uint_32_t | netinet/in.h |
in_port_t | 端口号,声明为 uint_16_t | netinet/in.h |
struct sockaddr
{
sa_family_t sin_family; //地址族
char sa_data[14]; //地址信息,包括IP地址和端口号,剩余部分填充为0
}
网络字节序和地址变换
CPU保存数据方式有两种:1. 大端序(高位字节存放到低位地址) 2. 小端序(高位字节存放到高位地址)
例如0x123456,大端序为 0x12345678 小端序为 0x78563412
为保证数据正常接收,电脑都是先把数组转换为大端序
再进行网络传输。网络字节序是大端序
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
htons 的 h 代表主机(host)字节序。transport short data from host to network
htons 的 n 代表网络(network)字节序。 transport short data from network to host
s 代表 short
l 代表 long
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
unsigned short host_port = 0x1234;
unsigned short net_port = htons(host_port); //转换为网络字节序;
unsigned long host_addr = 0x12345678;
unsigned long net_addr = htonl(host_addr);
printf("Host ordered port: %#x \n", host_port);
printf("Network ordered port: %#x \n", net_port);
printf("Host ordered address: %#lx \n", host_addr);
printf("Network ordered address: %#lx \n", net_addr);
return 0;
}
假设在小端序cpu上运行
Host ordered port: 0x1234
Network ordered port: 0x3412
Host ordered address: 0x12345678
Network ordered address: 0x78563412
网络地址的初始化与分配
sockaddr_in 保存地址信息的是 32 位整数,我们要将点分十进制表示的 IP 地址转换为 32 位整数可以通过
#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);
//成功时返回32位大端序整数,失败时返回InADDR_NONE
实例:
#include <stdio.h>
#include <arpa/inet.h>
int main(