知识了解:
IPv4(Internet Protocol version4):
- 使用一个32位的整型数描述一个IP地址,4个字节,int型
- 也可以使用一个点分十进制字符串描述这个IP地址: 192.168.247.135
- 分成了4份,每份1字节,8bit(char),最大值为255
- 0.0.0.0 是最小的IP地址
- 255.255.255.255是最大的IP地址
- 按照IPv4协议计算,可以使用的IP地址共有2^32个
IPv6(Internet Protocol version6):
- 使用一个128位的整型数描述一个IP地址,16个字节
- 也可以使用一个字符串描述这个IP地址: 2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b
- 分成了8份,每份2字节,每一部分以16进制的方式表示
- 按照IPv6协议计算,可以使用的IP地址共有2^18个数
端口的作用是定位到主机上的某一个进程,通过这个端口进程就可以接收到对应的数据了。
比如:在电脑上运行了微信和QQ,小明通过客户端给我的微信发消息,电脑上的微信就收到了消息,为什么?
- 运行在电脑上的微信和QQ都绑定了不同的端口
- 通过IP地址可以定位到某一台主机,通过端口就可以定位到主机上的某一个进程
- 通过指定的IP和端口,发送数据的时候对端就能接收到数据了
端口也是一个整形数,unsigned short,一个16位整型数,有效端口的取值范围是:0~65535(0~2^16-1)
提问:计算机中所有的进程都需要关联一个端口吗?一个端口可以被重复使用吗?
- 不需要,如果这个进程不需要网络通信,那么这个进程就不需要绑定端口的
- 一个端口只能给某一个进程使用,多个进程不能同时使用同一个端口
当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然错误的解释之。解决问题的方法是:发送端总是把要发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。
网络字节顺序是 TCP/IP 中规定好的一种数据表示格式,它与具体的 CPU 类型、操作系统等无关,从而 可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序采用大端排序方式。
BSD Socket提供了封装好的转换接口,方便程序员使用。包括从主机字节序到网络字节序的转换函数: htons、htonl;从网络字节序到主机字节序的转换函数:ntohs、ntohl。
h - host 主机,主机字节序
to - 转换成什么
n - network 网络字节序
s - short unsigned short
l - long unsigned int
#include
// 转换端口
uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序
uint16_t ntohs(uint16_t netshort); // 网络字节序 - 主机字节序
// 转IP
uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序
uint32_t ntohl(uint32_t netlong); // 网络字节序 - 主机字节序
socket地址其实是一个结构体,封装端口号和IP等信息。后面的socket相关的api中需要使用到这个socket地址。客户端 -> 服务器(IP, Port)
#include
// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);
af:地址族: AF_INET AF_INET6
src:需要转换的点分十进制的IP字符串
dst:转换后的结果保存在这个里面
// 将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af:地址族: AF_INET AF_INET6
src: 要转换的ip的整数的地址
dst: 转换成IP地址字符串保存的地方
size:第三个参数的大小(数组的大小)
返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
// TCP 通信的流程
// 服务器端 (被动接受连接的角色)
1. 创建一个用于监听的套接字
- 监听:监听有客户端的连接
- 套接字:这个套接字其实就是一个文件描述符
2. 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)
- 客户端连接服务器的时候使用的就是这个IP和端口
3. 设置监听,监听的fd开始工作
4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字
(fd)
5. 通信
- 接收数据
- 发送数据
6. 通信结束,断开连接
// 客户端
1. 创建一个用于通信的套接字(fd)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信
- 接收数据
- 发送数据
4. 通信结束,断开连接
server.c
#include
#include
#include
#include
#include
int main() {
// 1.创建监听的套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd == -1) {
perror("socket");
return -1;
}
// 2.绑定本地的IP port
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY; // 0 = 0.0.0.0 对于0来说,大端和小端是没有区别的的,因此不需要转换
saddr.sin_port = htons(9999);//主机字节序转换成网络字节序
int ret = bind(fd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("bind");
return -1;
}
// 3.设置监听
ret = listen(fd,128);
if(ret == -1) {
perror("listen");
return -1;
}
// 4.阻塞并等待客户端的连接
struct sockaddr_in caddr;
int addrlen = sizeof(caddr);
int cfd = accept(fd,(struct sockaddr*)&caddr,&addrlen);
if(cfd == -1) {
perror("accept");
return -1;
}
// 连接建立成功,打印客户端的IP和端口信息
char ip[32];
printf("客户端的IP: %s,端口: %d\n",
inet_ntop(AF_INET,&caddr.sin_addr.s_addr,ip,sizeof(ip)),
ntohs(caddr.sin_port));
// 5.通信
while(1) {
// 接收数据
char buff[1024];
int len = recv(cfd,buff,sizeof(buff),0);
if(len > 0) {
printf("client say: %s\n",buff);
send(cfd,buff,len,0);
}else if(len == 0) {
printf("客户端已经断开了连接...\n");
break;
}else{
perror("recv");
break;
}
}
// 关掉文件描述符
close(fd);
close(cfd);
return 0;
}
client.c
#include
#include
#include
#include
#include
int main() {
// 1.创建套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd == -1) {
perror("socket");
return -1;
}
// 2.连接服务器IP port
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET,"192.168.88.129",&saddr.sin_addr.s_addr);
int ret = connect(fd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("connect");
return -1;
}
int number = 0;
// 3.通信
while(1) {
// 发送数据
char buff[1024];
sprintf(buff,"你好,呵呵哒,%d...\n",number++);
send(fd,buff,strlen(buff) + 1,0);
//接收数据
memset(buff,0,sizeof(buff));
int len = recv(fd,buff,sizeof(buff),0);
if(len > 0) {
printf("server say: %s\n",buff);
}else if(len == 0) {
printf("服务器已经断开了连接...\n");
break;
}else{
perror("recv");
}
sleep(1);
}
// 关闭文件描述符
close(fd);
return 0;
}