TCP/IP网络编程学习笔记(二)套接字类型与协议设置、地址族与数据序列
时间:2019-04-15
本文章向大家介绍TCP/IP网络编程学习笔记(二)套接字类型与协议设置、地址族与数据序列,主要包括TCP/IP网络编程学习笔记(二)套接字类型与协议设置、地址族与数据序列使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
一、协议、协议族、套接字类型
1.概念
- 协议,Protocol:计算机之间的通信规则
- 协议族,Protocol Family,头文件sys/socket.h中声明的协议族如下:
PF_INET | IPv4互联网协议族 |
PF_INET6 | IPv6互联网协议族 |
PF_LOCAL | 本地通信的UNIX协议族 |
PF_PACKET | 底层套接字的协议族 |
PF_IPX |
IPX Novell协议族 |
- 套接字类型:套接字的数据传输方式,有两种:面向连接的套接字(SOCK_STREAM),面向消息的套接字(SOCK_DGRAM)
2.面向连接的套接字(SOCK_STREAM)
可靠的、按序传递的、基于字节的面向连接的数据传输方式的套接字
- 传输过程中数据不会消失
- 按序传输数据
- 传输的数据不存在数据边界(收发数据的套接字内部有 buffer,传输数据的计算机通过3次调用 write 函数传递了100字节的数据,但接收数据的计算机可能通过1次 read 函数调用就接收了全部100个字节,也可能多次调用)
// tcp套接字
int tcp_socket = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
3.面向消息的套接字(SOCK_DGRAM)
不可靠的、不按序传递的、以数据的高速传输为目的的套接字
- 强调快速传输而非传输顺序
- 传输的数据可能丢失也可能损毁
- 传输的数据有数据边界(接收数据的次数与传输次数相同)
- 限制每次传输的数据大小
// udp套接字
int udp_socket = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
4.验证数据边界代码
- 服务器端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
void error_handling(char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char* argv[]) {
int serv_sock; // 服务器端套接字描述符
int clnt_sock; // 客户端套接字描述符
struct sockaddr_in serv_addr; // 保存服务器端地址信息
struct sockaddr_in clnt_addr; // 保存客户端地址信息
char message[] = "Hello world!";
if (argc != 2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
// 调用 socket 函数创建套接字
// 此时的套接字不马上区分服务器端和客户端,若后面调用 bind、listen 函数,则成为服务器端套接字
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == -1)
error_handling("socket() error");
// 将 serv_addr 所指向的内存空间初始化为0
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 指定地址族
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // IP地址初始化,利用常数 INADDR_ANY 自动获取服务器端的计算机IP地址
serv_addr.sin_port = htons(atoi(argv[1])); // 端口号初始化
// 调用 bind 函数分配 IP地址和端口号
if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
// 调用 listen 函数将套接字转为可接收连接状态
if (listen(serv_sock, 5) == -1)
error_handling("listen() error");
socklen_t clnt_addr_size = sizeof(clnt_addr);
// 调用 accept 函数受理连接请求
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1)
error_handling("accept() error");
// 调用 write 函数向客户端传递数据
write(clnt_sock, message, sizeof(message));
close(clnt_sock); // 关闭客户端套接字
close(serv_sock); // 关闭服务器端套接字
return 0;
}
- 客户端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
void error_handling(char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char* argv[]) {
int sock; // 客户端套接字描述符
struct sockaddr_in serv_addr;
char message[30];
if (argc != 3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
// 调用 socket 函数创建套接字
// 此时的套接字并不马上分为服务器端和客户端,若后面调用 connect 函数,则成为客户端套接字
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1)
error_handling("scoket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 指定地址族
serv_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址初始化
serv_addr.sin_port = htons(atoi(argv[2])); // 端口号初始化
// 调用 connect 函数向服务器端发出连接请求
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("connect() error");
int str_len = 0;
// 调用 read 函数获取服务器端传输来的数据
while (read_len = read(sock, &message[idx++], 1)) {
if (read_len == -1)
error_handling("read() error");
str_len += read_len;
}
printf("Message from server:%s \n", message);
printf("Function read call count: %d \n",str_len);
close(sock); // 关闭客户端套接字
return 0;
}
- 运行结果:客户端调用了13次read()函数,说明TCP传输不存在数据边界
二、IP地址、地址族
1.概念
- 网络协议,IP,Internet Protocol,有IPv4(4字节)和IPv6(16字节)两类
- 网络地址,Internet Address
- 主机地址,Host Address
- 回送地址:127.0.0.1
2.IPv4
- 分为ABCDE五类
- 网络地址+主机地址
- 网络地址分类与主机地址边界:A类地址的首字节范围:0~127,首位以0开始;B类地址的首字节范围:128~191,前两位以10开始;C类地址的首字节范围:192~223,前三位以110开始
3.用于区分套接字的端口号
- 16位,0~65535,其中0~1023是Well-known PORT,不可使用
- 同种协议套接字的端口号不允许重复,TCP套接字和UDP套接字不会公用端口号,允许重复
4.地址信息的表示
struct sockaddr_in{
sa_family_t sin_family; // 地址族
uint16_t sin_prot; // 16位TCP/UDP端口号,以网络字节序保存
struct in_addr sin_addr; // 32位IPv4地址,以网络字节序保存
char sin_zeros[8]; // 不使用,只是为使sockaddr_in结构体的大小与sockaddr结构体保持一致而插入的成员
}
struct in_addr{
in_addr_t s_addr; // 32位IPv4地址
}
5.字节序
- 大端序:高位字节存放在低位地址(从左到右,从低到高)
- 小端序:高位字节存放在高位地址(从右到左,从低到高)
- 网络字节序:统一为大端序!!!
- Intel和AMD系列的CPU都采用小端序,小端序系统传输数据时应先转换为大端序排列方式。
- 除了向 sockaddr_in 结构体变量填充数据外,其他情况无需考虑字节序问题
- 利用常数 INADDR_ANY 分配服务器端的IP地址,则可自动获取运行服务器端的计算机的IP地址
6.字节序转换函数
#include<arpa/inet.h>
// h表示 host,n表示 network,s表示 short,l表示 long
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
#include<stdio.h>
#include<arpa/inet.h>
int main(int argc, char *argv[]) {
unsigned short host_port = 0x1234;
unsigned short net_port;
unsigned long host_addr = 0x12345678;
unsigned long net_addr;
net_port = htons(host_port);
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;
}
运行结果:
Host ordered port: 0x1234
Network ordered port: 0x3412
Host ordered address: 0x12345678
Network ordered address: 0x78563412
6.IP地址字符串(点分十进制表示法)与32位整型值的相互转换
#include<arpa/inet.h>
in_addr_t inet_addr(const char* cstr); // 成功时返回 32 位大端序整型值,失败时返回 INADDR_NONE
#include<arpa/inet.h>
int inet_aton(const char* cstr,struct in_addr* addr); // 成功时返回 1,失败时返回 0
#include<apra/inet.h>
char* inet_ntoa(struct in_addr addr); // 成功时返回转换的字符串地址,失败时返回-1
// 调用时需十分小心,因为该函数返回指针,但并未向程序员要求分配内存,所以调用完需将字符串复制到其他内存空间,否则下一次调用可能覆盖上次调用的结果
#include<stdio.h>
#include<arpa/inet.h>
int main(int argc, char* argv[]) {
char *addr[2] = { "1.2.3.4","1.2.3.256" };
unsigned long conv_addr;
for (int i = 0; i < 2; i++) {
conv_addr = inet_addr(addr[i]);
if (conv_addr == INADDR_NONE) {
printf("Invalid IP!\n");
}
else {
printf("Network ordered integer addr: %#lx \n", conv_addr);
}
}
return 0;
}
运行结果:
Network ordered integer addr: 0x4030201
Invalid IP!
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
int main(int argc, char* argv[]) {
const char* addr[2] = { "1.2.3.4","1.2.3.256" };
struct sockaddr_in addr_inet;
for (int i = 0; i < 2; i++) {
if (!inet_aton(addr[i], (struct in_addr*)&addr_inet.sin_addr)) {
fputs("Conversion error!", stderr);
fputc('\n', stderr);
exit(1);
}
else {
printf("Network ordered integer addr: %#x \n", addr_inet.sin_addr.s_addr);
}
}
return 0;
}
运行结果:
Network ordered integer addr: 0x4030201
Conversion error!
#include<stdio.h>
#include<string.h>
#include<arpa/inet.h>
int main(int argc, char* argv[]) {
struct sockaddr_in addr1, addr2;
char* str_ptr;
char str_arr[20];
addr1.sin_addr.s_addr = htonl(0x1020304);
addr2.sin_addr.s_addr = htonl(0x1010101);
str_ptr = inet_ntoa(addr1.sin_addr);
strcpy(str_arr, str_ptr);
printf("Dotted-Decimal notation1: %s \n", str_ptr);
inet_ntoa(addr2.sin_addr);
printf("Dotted-Decimal notation1: %s \n", str_ptr);
printf("Dotted-Decimal notation2: %s \n", str_arr);
return 0;
}
运行结果:
Dotted-Decimal notation1: 1.2.3.4
Dotted-Decimal notation1: 1.1.1.1
Dotted-Decimal notation2: 1.2.3.4
参考书籍:《TCP/IP网络编程》尹圣雨 著,金果哲 译
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android开发之使用SQLite存储数据的方法分析
- Android进度条控件progressbar使用方法详解
- Android自定义view利用Xfermode实现动态文字加载动画
- Android自定义控件实现下拉刷新效果
- Android实现文件解压带进度条功能
- Android使用ViewPager实现图片滑动预览效果
- Android 布局中的android:onClick的使用方法总结
- Android 提交或者上传数据时的dialog弹框动画效果
- Android 调用系统应用的方法总结
- Android实现网易严选标签栏滑动效果
- Android socket实现原理详解 服务端和客户端如何搭建
- Android使用ViewFlipper实现图片切换功能
- Android 图片显示与屏幕适配的问题
- Android实现截图和分享功能的代码
- Android 自定义相机及分析源码