网络如何通信
实际还是进程通信,不过是不同主机间进程的通信
协议的初始
网络协议
OSI && TCP 模型
每层的 典型协议 / 功能 / 设备
ip地址 && mac地址
ip地址是路由器分配给我们的
而mac地址是我们设备在出厂时 厂家我们设置的物理标识符//全球仅此一台
换个主机插入该网段,仍然会获得 路由器为我们划分的ip(可能与被拔的主机ip地址相同)
但是换上来的主机有它自己独一无二的标识符,独一无二的mac地址
主机A给主机B发送消息,中途换下主机B换上主机F,我们B的消息无法发送给A(因为到了底层设备我们)
看的是mac地址
win + r 打开我们的command,输入ipconfig 看我们的ip
linux操作系统是 ifconfig 可以看ip及mac地址
插播
ip封禁对于我们基本无效,而是像网吧这种固定ip就要遭殃了
而我们DHCP可以为我们分配本地的ip(我们可以给它设置一个范围),我们的IP是变化的
游戏公司唯一要做的,就是记录你的硬件信息,比如说机器码,这个机器码就是
根据你的硬件的唯一序列号生成的一串代码。如果是封的你的CPU或主板,那你
基本是无法通过更换硬件的方式解决封号的问题,换一次成本太大。如果是封机
器码,那么机器码是根据硬件信息生成的,可能包含了cpu主板显卡与硬盘,一
旦包含了硬盘,那么更换一个硬盘,机器码就变了,相当于换了电脑,即可低成
本解封。或者使用软件或手段让游戏读取一个虚拟的硬件信息即可绕过设备封禁
port(端口)
本质: 端口号是一个2字节16位的整数
范围:[0,65535]
知名端口
这些端口我们程序员写的程序不要轻易绑定
0~1023
oracle:1521
mysql:3306
端口号与进程绑定,如果你给错了端口,比如进程A拥有443号端口 功能是访问网页
你给了20号端口他在进程B,进程B不具备处理访问网页的能力,无法达到我们预期的目的
大端&&小端
#include<iostream>
#include<time.h>
#include<stdio.h>
using namespace std;
void main()
{
unsigned int bytes = 1;
cout << (*((char*)(&bytes)) ? "little-endian" : "big-endian") << endl;
}
//取int 4字节 强转char 1字节 如果是小端char 就能get到数据
TCP && UDP
UDP:
无连接:UDP双方在发送数据之前, 是不需要进行沟通的。 只需要知道对方的ip和端口就好(可能对方进程还没有准备好)
不可靠:不保证UDP数据是可靠, 有序的到达对方
面向数据报: UDP和应用层/网络层递交数据的时候, 都是整条数据进行交付的就可以发送
TCP:
面向连接: TCP双方在发送数据之前会先建立连接。
(1.确保对方正常能通信,2.沟通双发发送后续数据的细节(例如序号)
可靠传输: TCP保证传输的数据是可靠、有序的到达对端的
面向字节流:
1.对于传输的数据没有明显的边界
2.对于接收方而言, 可以按照任意的字节进行接收
udp-socket编程
客户端------服务端
创建套接字的含义:
将进程和网卡进行绑定, 进程可以从网络协议栈当中接收或者发送数据
绑定地址信息的含义:
给进程绑定ip,绑定端口。为了在全网当中标识一台主机和一个进程
要用到的一些接口
创建套接字的接口:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:
地址域 - 选择一个具体的协议族进行沟通,对于我们而言,(udp/tcp)可以认为在指定网络层使用什么协议
AF_UNIX: 本地域套接字(在同一台机器使用文件进行通信, 不用跨机器)
AF_INET:ipv4版本的ip协议
AF_INET6 :ipv6版本的ip协议
这三个都是宏定义,ipv6的诞生解决了ipv4网络地址不够用的问题(不是子网划分解决的)
[0 , (2^32)-1] [0 , (2^128)-1] 地址呈指数般增加,如同一口井,变成了汪洋大海;
-----------------------------------------------------------------------------------------
type: 套接字的类型
SOCK_DGRAM : 用户数据报套接字 - 对应UDP
SOCK_STREAM : 流式套接字 - 对应TCP
-----------------------------------------------------------------------------------------
protocol: 协议
0: 表示按照 套接字类型选择默认协议
SOCK_DGRAM 对应UDP协议
SOCK_STREAM 对应TCP协议
也可以执行具体的协议:
IPPROTO_TCP (6) :代表tcp协议
IPPROTO_UDP (17) :代表udp协议
-----------------------------------------------------------------------------------------
返回值: 返回套接字操作句柄, 本质上就是一个文件描述符
大于等于0 : 创建成功
小于0: 创建失败
绑定接口
绑定接口:
int bind(int sockfd, const struct sockaddr *addr , socklen_t addrlen)
sockfd :套接字描述符
addr:绑定的地址信息
struct sockaddr:内核描述地址信息的通用结构体
地址信息:
AF_INET:tcp/udp开发而言: ip,port
AF_UNIX.文件的路径
addrlen:绑定的地址信息长度
bind函数的 const struct sockaddr *addr可以通过前两个字节16位地址类型
(我们的宏),
知道我们绑定的是谁的接口,是UDP还是TCP还是我们本地主机
发送消息
发送消息:
ssize_t sendto( int sockfd , const void *buf , size_t len , int flags ,
const struct sockaddr *dest_addr , socklen_t addrlen);
sockfd : 套接字描述符
buf : 要发送的数据
len : 发送数据的长度
flags : 0 (阻塞发送)
dest_addr : 对端地址信息结构,包含对端主机的IP,端口
addrlen : 对端地址信息结构长度
通用的数据结构
struct sockaddr in{...}; 目标主机的Ip目的端口
返回值:
真正发送的数据长度,单位字节
接收消息
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr,socklen_t *addrlen);
sockfd : 套接字描述符
buf : 接收到的数据保存到buf指向的空间当中, buf指向的空间需要程序员提前开辟
len : 最多可以接收多少数据
flags : 0 (阻塞接收)
src_addr : 接收到的数据从哪里来, 对端的地址信息结构
客户端调用,接受到的就是服务端的地址信息结构
服务端调用,接收到的就是客户端的地址信息结构
addrlen : 绑定的地址信息长度
返回值:
真正接受到数据长度,单位字节
关闭套接字
int close(int fd)
服务端进程代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
//这三个头文件是套接字编程需要包含的头文件
int main(){
/*
* 1.创建套接字
* 2.绑定地址信息
* 3.收发数据
* 4.关闭套接字
* */
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0){
return 0;
}
//addr : 服务端绑定的地址信息结构, 保存着服务端需要绑定的ip地址和端口
struct sockaddr_in addr;
// 填充地址类型, 后续bind函数解析了地址类型之后, 就知道传入的是哪一个数据结构了
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);// 填充端口, 一定要注意将主机字节序转换为网络字节序
// 填充ip地址
// addr.sin_addr.s_addr : 接收的是无符号32位的整数(ip地址)
// in_addr_t inet_addr(const char *cp);
// 参数:字符指针, 接收一个点分十进制的字符串
// 功能:将点分十进制的ip地址转换为无符号32位的整数
// 将无符号32位的整数从主机字节序转换为网络字节序
// "0.0.0.0" : 绑定本机的任意网卡
// 容易犯错的地方: 绑定了云服务器的公网ip, 这个是万万不行的
// 绑定云服务器的私网ip地址, 还可以
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0){
return 0;
}
//能接收, 但是没有给客户端进行响应
while(1){
char buf[1024] = {0};
struct sockaddr_in peer_addr;
socklen_t peer_addrlen = sizeof(peer_addr);
recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer_addr, &peer_addrlen);
printf("[buf is ] %s from %s, %d\n", buf, inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
/*
* ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
* */
memset(buf, '\0', sizeof(buf));
printf("please enter msg: ");
fflush(stdout);
std::cin >> buf;
//sprintf(buf, "hello, %s:%d, i am server", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&peer_addr, sizeof(peer_addr));
}
close(sockfd);
return 0;
}
客户端进程代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
int main(){
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0){
perror("socket");
return 0;
}
//struct sockaddr_in b_addr;
//b_addr.sin_family = AF_INET;
//b_addr.sin_port = htons(10010);
//b_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
//int ret = bind(sockfd, (struct sockaddr*)&b_addr, sizeof(b_addr));
//if(ret < 0){
// perror("bind");
// return 0;
//}
/*
* ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
*/
const char* str = "i am client...";
struct sockaddr_in addr;
addr.sin_family = AF_INET;
// 这里填充的是服务端的端口, 因为数据是发送到服务端的
addr.sin_port = htons(8080);
// 这里填充的是服务端的ip
// 本地回环地址
addr.sin_addr.s_addr = inet_addr("120.78.126.148");//改成自己的内网ip
//addr.sin_addr.s_addr = inet_addr("127.0.0.1");
while(1){
char buf[1024] = {0};
printf("please enter msg: ");
fflush(stdout);
std::cin >> buf;
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&addr, sizeof(addr));
/*
* ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
* */
//相当于不要recvfrom函数返回服务端的地址信息,以及地址信息的长度, 因为
//客户端是清楚服务端的地址信息的
memset(buf, '\0', sizeof(buf));
recvfrom(sockfd, buf, sizeof(buf) - 1, 0, NULL, NULL);
printf("[buf is] %s\n", buf);
sleep(1);
}
close(sockfd);
return 0;
}
1.关注客户端发送数据的时候,给的ip和端口到底对不对
2.看防火墙
下面是阿里云的服务器操作方法
看自己防火墙
客户端可以绑定地址信息,但是就不能开多个客户端了,
因为端口号被第一个客户端占有,后面的客户端不改
端口号是打不开新的客户端的
改好了就可以用我们的服务端同客户端发送消息了