Linux:网络套接字
理解源IP地址和目的IP地址
在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.
思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上, 但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析
端口号
端口号(port)是传输层协议的内容.
端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用.
另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定
网络套接字
套接字 = IP + 端口
网络通信的本质:
其实就是进程间通信! !
真正通信的不是这两个机器 !
其实是这两台机器上面的软件(人)
数据有IP(公网)标识一台唯一的主机
用谁来标识各自主机上客户或者服务进程的唯一性呢 ?
为了更好的表示一台主机上服务进程的唯一性,我们采用端口号port
标识服务器进程,客户端进程的唯一性!
ip地址(主机全网唯一性)+ 该主机上的端口号,标识该服务器上进程的唯一性
套接字用来标识全网中唯一的一个进程。
socket 常见API
TCP协议
传输层协议
有连接
可靠传输
面向字节流
UDP协议
传输层协议
无连接
不可靠传输
面向数据报
网络字节序
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络 字节序和主机字节序的转换
netstat -nuap查看端口使用情况。
UDP通信小程序
udpServer.hpp
#include<iostream>
#include<string>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
namespace szg
{
string default_ip = "0.0.0.0";
class udpServer
{
public:
udpServer(uint16_t port, string ip = default_ip)
:_ip(ip)
,_port(port)
{
//创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
cout << "socket failed !!!!" << endl;
exit(1);
}
cout << "socket sucess :" << _sockfd << endl;
}
void server_init()
{
//绑定套接字
sockaddr_in tmp;
bzero(&tmp, sizeof(tmp));
tmp.sin_family = AF_INET;
tmp.sin_port = htons(_port);//用户端字节序转网络字节序
tmp.sin_addr.s_addr = inet_addr(_ip.c_str());//IP地址点十进制转32位大端
cout << "port :" << _port << " ip :" << _ip << endl;
int rb = bind(_sockfd, (sockaddr*)&tmp, sizeof(tmp));
if(rb == -1)
{
cout << " bind fail !!!" << endl;
exit(2);
}
}
void server_start()
{
char buffer[1024];
sockaddr_in peer;
while(_quit)
{
socklen_t len = sizeof(peer);
//cout << "准备读取" << endl;
ssize_t rrec = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr*)&peer, &len);
if(rrec == 0)
{
cout << "receive fail !!!" << endl;
}
buffer[rrec] = 0;
cout << inet_ntoa(peer.sin_addr) << "[" << ntohs(peer.sin_port) << "] : " << buffer << endl;//inet_ntoa大端序列32位转点十进制
}
}
~udpServer()
{}
private:
string _ip;
uint16_t _port;
int _sockfd;
bool _quit = true;
};
}
udpServer.cc
#include"udpServer.hpp"
#include<memory>
int main(int args, char* argv[])
{
if(args != 2)
{
cout << "udpServer init fail : \n\t" << argv[0] << "portn\n\n";
exit(3);
}
unique_ptr<szg::udpServer> server(new szg::udpServer((uint16_t)atoi(argv[1])));
server->server_init();
server->server_start();
return 0;
}
udpClient.hpp
#include<iostream>
#include<string>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
namespace szg
{
class udpClient
{
public:
udpClient(string ip, uint16_t port)
:_ip(ip)
,_port(port)
{
//创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
cout << "socket failed !!!!" << endl;
exit(1);
}
cout << "socket sucess :" << _sockfd << endl;
}
void client_start()
{
string buffer;
sockaddr_in peer;
bzero(&peer, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = inet_addr(_ip.c_str());
peer.sin_port = htons(_port);
while(_quit)
{
cout << "client#: ";
cin >> buffer;
int n = sendto(_sockfd, buffer.c_str(), buffer.size(), 0, (sockaddr*)&peer, sizeof(peer));
if(-1 == n)
{
cout << "sendto fail !!!\n";
}
}
}
~udpClient()
{}
private:
string _ip;
uint16_t _port;
int _sockfd;
bool _quit = true;
};
}
udpClient.cc
#include"udpClient.hpp"
#include<memory>
int main(int args, char* argv[])
{
if(args != 3)
{
cout << "\nUsage:\n\t" << argv[0] << " _ip _port\n\n";
}
unique_ptr<szg::udpClient> client(new szg::udpClient(argv[1], (uint16_t)atoi(argv[2])));
client->client_start();
return 0;
}