预备知识:
端口号port:
我们在正常网络通信时,实际上是进程在互相通信。
我们所有的网络通信的行为,本质上都是进程间通信。
对双方而言,1.先保证数据能到达自己的机器ip解决 2.找到指定的进程 端口号
ip地址用来标识互联网中唯一一台主机
端口号用来表示该指定的主机的进程的唯一性
所以ip+port={ip,port}互联网中唯一一个进程
{ip,port}我们把它叫做套接字,socket
**如何理解端口号?**uint16_t port
16位的数字,uint16_t port,用来标识主机的唯一的一个网络进程
为什么不直接用pid来表示网络中唯一一个进程
1.进程管理和网络管理进行解耦
2.port用来专门为网络通信
一个端口和一个进程进行关联?
一个进程可以和多个端口号关联,但是多个进程不能和一个端口号关联。
ip也可以对应多个端口号。
TCP和UDP
TCP协议:面向链接,面向字节流,可靠通信
UDP协议:面向数据报,无连接,不可靠通信
既然有了可靠通信,为什么又要设计不可靠通信
中性词,可靠通信,要得到保证就必须做更多的工作(复杂),不可靠通信,做更少的工作,很简单,丢包率也不会太高。他们之间只有不同没有好坏。
网络字节序列
在存储上我们有大端存储(大权值在低地址)和小端存储
也就是说不同机器的存储方式是不同的
我们在发数据,在内存中是以低地址到高地址发送,接收主机在内存中读取也是从低地址到高地址接收
网络规定达到网络的数据,都以大端发送,所有机器都知道数据是以大端发送的,所以小端机器就大端转化
到小端。因为主机的大小端是不一样的。
htonl:host to net int
因为port需要通过网络递送,所以得转成网络序列
socket:网路编程,socket是有很多类别的
1.unix socket :域间socket;同一台主机 的文件路径,在本主机进行通信
2.网络socket:ip+port:网络通信的文件
3.原始socket:编写一些网络工具
理论上以上三种类别都要有自己的一套接口
但是设计者只想用一套接口是一套通用的地址类型
Server服务器
创建套接字
return value;返回一个文件描述符
domain:网络通信的协议家族,也叫域
type:套接字的类型
protocol:tcp 或 udp
struct sockaddr_in local;
bzero(&lock,sizeof(local))
local.sin_family=AF_INET
local.sin_port=htons(_port)
local.sin_addr.s_addr=inet_addr(_ip)
完成下列功能
1.四字节ip 2.网络序列
创建了socket我们要把他绑定,将它与socket关联
sockfd:socket返回的文件描述符
addr:传入sockaddr_un 或者 sockaddr_in
addrlen:结构体长度
服务器永远不退出,在Start中注定是一个死循环、
服务器接收
返回的是实际字节,len的期望收到的字节,flags默认为0(阻塞收消息)
后两个参数为输入输出型参数:给我们客户端client的信息 ip port,以后发消息就发给这个客户端
服务器发送
cpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nocopy.hpp"
#include "Log.hpp"
#include "Comm.hpp"
using namespace std;
const uint16_t defaultport = 8888;
const int defaultsocketfd = -1;
const int buffsize = 1024;
class Udpserver : public nocopy
{
public:
Udpserver(const uint16_t port = defaultport)
: _port(port), _socketfd(defaultsocketfd)
{
}
void Init()
{
// socket
_socketfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_socketfd < 0)
{
lg.LogMessage(Fatal, "socket errr, %d : %s\n", errno, strerror(errno));
cout << endl;
exit(socketfderror);
}
lg.LogMessage(Info, "socketfd success \n");
cout << endl;
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
// bind
int n = ::bind(_socketfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
lg.LogMessage(Fatal, "bind error,%d : %s\n", errno, strerror(errno));
cout << endl;
exit(binderror);
}
lg.LogMessage(Info, "bind success\n");
cout << endl;
}
void Start()
{
for (;;)
{
struct sockaddr_in peer;
socklen_t peerlen = sizeof(peer);
char rbuff[1024];
ssize_t n = recvfrom(_socketfd, rbuff, sizeof(rbuff - 1), 0, (struct sockaddr *)&peer, &peerlen);
rbuff[n] = '\0';
cout << "recv info:" << rbuff << endl;
// if (n < 0)
// {
// lg.LogMessage(Fatal, "recvfrom error,%d : %s\n", errno, strerror(errno));
// }
// rbuff[n]='\0';
ssize_t sendn = sendto(_socketfd, rbuff, sizeof(buffsize), 0, (struct sockaddr *)&peer, peerlen);
cout << "sendn success:" << rbuff << endl;
if (n < 0)
{
lg.LogMessage(Fatal, "sendto error,%d : %s\n", errno, strerror(errno));
}
}
}
~Udpserver() {}
private:
// string _ip;
uint16_t _port;
int _socketfd;
};
netstat: 查看网络连接 -anup
Client客户端
创建套接字
client需不需要绑定,一定需要,但不需要显示绑定,client向服务器第一次发送信息时会自动绑定
为什么?
server的port众所周知是不可以随意改变的,client的port是随机端口,这是由于启动客户端的进程是很多
的,
127.0.0.1:本地循环,通常用来网络cs的测试
我们想看到客户端的信息应该如何看到?
绑定云服务器地址失败?
云服务器的公网ip是虚拟的,无法直接bind,不推荐,更推荐本地任意ip的绑定方式
如何进行服务端的任意本地ip的绑定
IP=INADDR_ANY 实现动态绑定 0.0.0.0表示任意绑定
cpp
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "Comm.hpp"
const int defaultsrecbuff = 1024;
using namespace std;
void Usage(char *argv)
{
cout << "Usage:" << argv << "+ServerIp+ServerPort" << endl;
}
int main(int argc, char *argv[])
{
if (argc < 3)
{
Usage(argv[0]);
cerr << "use client error" << endl;
return Usage_Err;
}
// 创建套接字
int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
// 填充server 发送信息
string serverip = argv[1];
uint16_t serverport = stoi(argv[2]);
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
while (true)
{
string buff;
cout << "#Please enter:";
getline(cin, buff);
int n = sendto(socketfd, buff.c_str(), buff.size(), 0,
(const struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
cerr << "send error" << errno << strerror(errno);
break;
}
else
{
// 接收信息
struct sockaddr_in peer;
socklen_t peerlen = sizeof(peer);
char recbuff[defaultsrecbuff];
int m = recvfrom(socketfd, recbuff, sizeof(recbuff)-1, 0,
(struct sockaddr *)&peer, &peerlen);
if (m < 0)
{
cerr << "rec error" << errno << strerror(errno);
break;
}
else
{
recbuff[m]=0;
cout << "#server say:" << recbuff << endl;
}
}
}
close(socketfd);
}