计算机网络开发(2)TCP\UDP区别、TCP通信框架、服务端客户端通信实例
TCP与UDP区别
UDP
:用户数据报协议,面向无连接,可以单播,多播,广播, 面向数据报,不可靠TCP
:传输控制协议,面向连接的,可靠的,基于字节流,仅支持单播传输
socket通信
是对网络中不同主机上的应用程序进程之间进行双向通信的端点的抽象
字节序
**小端字节序:**低位存在内存的低位
大端字节序:低位存在内存的高位
eg:小端
0x01 02 03 04
01是高位有效字节,04是低位,那么在内存中是这么存的:
0x04 0x03 0x02 0x01
字节序转化内函数
h - host 主机,主机字节序
to - 转换成什么
n - network 网络字节序
s - short unsigned short
l - long unsigned int
#include <arpa/inet.h>
// 转换端口 short型,2个字节
uint16_t htons(uint16_t hostshort); // 主机字节序 => 网络字节序
uint16_t ntohs(uint16_t netshort); // 主机字节序 => 网络字节序
// 转IP int型,4个字节
uint32_t htonl(uint32_t hostlong); // 主机字节序 => 网络字节序
uint32_t ntohl(uint32_t netlong); // 主机字节序 => 网络字节序
ip地址转换------点分十进制<–>网络通信2进制
通常,人们习惯用可读性好的字符串来表示 IP 地址,比如用点分十进制字符串表示 IPv4 地址,以及用十六进制字符串表示 IPv6> 地址。但编程中我们需要先把它们转化为整数(二进制数)方能使用。而记录日志时则相反,我们要把整数表示的 IP 地址转化为可读的字符串
新版:同时适用于IPV4和IPV6
* 字母含义
* `p`:点分十进制的IP字符串
* `n`:表示network,网络字节序的整数
* `int inet_pton(int af, const char *src, void *dst); `
* 使用`man inet_pton`查看帮助
* 功能:将点分十进制的IP地址字符串,转换成网络字节序的整数
* 参数
* `af`:地址族
* IPV4:`AF_INET`
* IPV6:`AF_INET6(IPV6)`
* `src`:需要转换的点分十进制的IP字符串
* `dst`:转换后的结果保存在这个里面
* 返回值
* 1:成功
* 0:源IP地址有误
* -1:地址族包含不可用的地址协议
* `const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);`
* 使用`man inet_ntop`查看帮助
* 功能:将网络字节序的整数,转换成点分十进制的IP地址字符串
* 参数
* `af`:地址族
* IPV4:`AF_INET`
* IPV6:`AF_INET6(IPV6)`
* `src`:要转换的ip的整数的地址
* `dst`:转换成IP地址字符串保存的地方
* `size`:第三个参数的大小(数组的大小)
* 返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
把IP和port打包为一个socket地址
协议族 | 地址族 | 描述 |
---|---|---|
PF_UNIX | AF_UNIX | UNIX本地域协议族 |
PF_INET | AF_INET | TCP/IPV4z协议族 |
PF_INET6 | AF_INET6 | tcp/IPV6协议族 |
//socket网络通信接口中表示socket地址的是一个结构体,他的地址作为通信用,但一般用下面的改进版本,再转换成这个
#include <bits/socket.h>
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
typedef unsigned short int sa_family_t;
为了兼容IPV6,新的socketaddr_in
// IPV4
#include <netinet/in.h>
-----------------------------------------------------------------------
struct sockaddr_in {
sa_family_t sin_family; /* __SOCKADDR_COMMON(sin_) */
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)];
};
-----------------------------------------------------------------------
struct in_addr {
in_addr_t s_addr;
};
// IPV6
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* IPv6 scope-id */
};
// 相关定义
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
TCP通信框架
实例,客户端和服务端通信
服务端:
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string>
using namespace std;
#define SERVERIP "192.168.226.129"
#define PORT 9999
//服务器端
int main() {
//创建socket套接字,形成监听fd
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket");
return -1;
}
//形成一个端,把ip和port打包好在新版的sockaddr_in结构体中
struct sockaddr_in mServ;
mServ.sin_family = AF_INET;
mServ.sin_port = htons(PORT);
//将上面字符串形式的点分十进制IP转化为网络字节序的IP
inet_pton(AF_INET, SERVERIP, &mServ.sin_addr.s_addr);
//将上面的打包的端和监听fd绑定
int ret = bind(lfd, (struct sockaddr*)&mServ, sizeof(mServ));
if (ret == -1) {
perror("bind");
return -1;
}
//监听fd开始监听,后面的值代表未连接和连接的数
ret = listen(lfd, 128);
if (ret == -1) {
perror("listen");
return -1;
}
cout << "服务器启动成功,等待连接...." << endl;
//接受连接,返回一个新的真正用于交换数据的fd
struct sockaddr_in client;
socklen_t client_addr_len = sizeof(client);
int cfd = accept(lfd, (struct sockaddr*)&client, &client_addr_len);
if (cfd == -1) {
perror("accept");
return -1;
}
//输出客户端的ip和端口
char clinet_ip[16];
inet_ntop(AF_INET, &client.sin_addr.s_addr, clinet_ip, sizeof(clinet_ip));//将n->p的ip写到clinet_ip中
int clinet_port = ntohs(client.sin_port);
cout << "连接成功!客户端IP:" << clinet_ip << " 端口:" << clinet_port << endl;
//读取客户端的数据
char buf[1024];
char writer[1024];
while(1) {
int read_len = read(cfd, buf, sizeof(buf));
if (read_len == -1) {
perror("read");
return -1;
}else if (read_len == 0) {
cout << "客户端断开连接!" << endl;
break;
}else if (read_len >0) cout << "客户端发来的数据:" << buf << endl;
//向客户端发送数据
cout << "请输入要发送的数据:";
cin >> writer;
write(cfd, writer, sizeof(writer));
}
close(cfd);
close(lfd);
return 0;
}
客户端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string>
using namespace std;
#define SERVERIP "192.168.226.129"
#define PORT 9999
//客户端
int main() {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("client_socket");
return -1;
}
//打包client的IP和PORT信息
struct sockaddr_in clit;
clit.sin_family = AF_INET;
clit.sin_port = htons(PORT);
inet_pton(AF_INET, SERVERIP, &clit.sin_addr.s_addr);
//客户端开始连接服务器
int ret = connect(fd, (struct sockaddr*)&clit, sizeof(clit));
if (ret == -1) {
perror("client connect");
return -1;
}
cout << "客户端开始连接...." << endl;
//开始通信
char writer[1024];
char buf[1024];
while(1) {
cout << "请输入发给服务器的内容:";
cin >> writer;
ret = write(fd, writer, sizeof(writer));
if (ret == -1) {
perror("client write");
return -1;
}
ret = read(fd, buf, sizeof(buf));
if (ret == -1) {
perror("client read");
return -1;
} else if (ret == 0) {
cout << "服务器断开连接" << endl;
break;
} else cout << "受到客户端返回信息:" << buf << endl;
}
close(fd);
return 0;
}