Socket编程
Posted ych9527
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket编程相关的知识,希望对你有一定的参考价值。
1.源IP、目的IP地址
1.在ip数据包头部,有两个ip地址,分别叫做源ip地址和目的ip地址
2.源ip地址标记着数据是从哪里发出来的,目的ip地址表示发往那台主机
ip的作用是标识公网之中,主机的唯一性
2.端口号
2.1端口号基本概念
1.端口号是一个2字节16位的整数
2.端口号用来标识一个进程,告诉操作系统(硬件最先收到数据,因此这个数据要贯穿OS,最后由OS交给某个进程),当前的这个数据要要给哪一个进程来进行处理
3.ip地址+端口号能够标识网络上的某一台主机的某一个进程
4.一个端口号只能被一个进程占用,一个进程可以由多个端口号
2.2进一步理解端口号和pid的关系
进程pid:每一个进程都有
端口号:网络进程才有端口号、网络进程之中的表示
所以有:进程一定有pid标识这个进程,但是不一定有端口号,因为网络之中的进程才会被分配端口号
端口号+ip表示全网之中的唯一进程
端口号和pid一样都是进程的表示,所以一个端口号只能对应一个进程,而一个进程可以有多个端口号
2.3端口号划分
0-1023:知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议,他们的端口号都是固定的
1021-65535:操作系统动态分配的端口号,客户端程序的端口号,就是由操作系统从这个范围分配的
2.4认识知名端口号
1.ssh服务器,使用22端口
xshell只是提供了一层外壳,为用户提供了界面。 可以直接用ssh服务器ip和端口号对服务器进行访问
2.ftp服务器,使用21端口
3.telnet服务器,使用23端口
4.http服务器,使用80端口
5.https服务器,使用443端口
cat / etc/ services : 查看知名端口号
3.进一步理解网络通信
我们进行上网都需要打开软件(上网入口),比如游览器,当打开软件之时,将硬盘上的文件加载到内存之中,在客户端启动了客户端进程。然后通过网络寻找对应的服务器,进行数据的交互。
其实本质就是进程之间的通信
在这之中,端口号表示网络之中的唯一进程、ip标识网络之中的唯一主机 -》 port + ip = 网络之中的唯一进程,即形成了进程之间的通信
其中 ip+port就是套接字
4.TCP、UDP协议
4.1 TCP协议
面向连接,可靠传输,面向字节流
面向连接:TCP通信双方在发送数据之前,需先建立起连接,才能够发送数据
可靠传输:TCP保证传输的数据是可靠有序的到达对端
面向字节流:
1.对于传输的数据之间没有明显的数据边界(比如第一次发送123,第二次发送456,接收方是不能区分第一次发送的是什么)
2. 对于接收方而言,在可以接收数据的情况下,可以接收任意字节的数据(比如将12345,分好几次进行接收)
TCP是提供可靠的传输层协议,因此需要处理的事情就会更多,比如数据会不会丢失,丢失了怎么办等等,因此TCP协议就会更加的复杂,复杂的东西效率也会更低
4.2 UDP协议
无连接,不可靠,面向数据报
无连接:UDP通信双方在发送数据之前,是不需要进行沟通的,客户端只需要知道服务端的ip和端口,就可以直接发送数据了
不可靠:不保证数据是可靠到达对方的,并且不保证数据是按序到达(比如先发1在发2,结果是2先到的)
面向数据报:UDP对于传输层和应用层数据交递的时候,都是整条数据交付
DUP只负责数据传输,不保证数据是安全达到的,因此UDP协议比较简单,效率也更高
5.字节序
5.1主机字节序
字节序:CPU对内存的访问顺序
小端字节序:低位放低地址
大端字节序:低位放高地址
主机字节序:指的是机器本身的字节序,如果是大端,则主机字节序就是大端;如果是小端,主机字节序就是小端
ps:
通常字节序和计算机的架构有关系,最常见的x_86体系架构是小端机器
当前95%机器都是小端机器
5.2网络字节序
网络字节序:规定网络中传输的字节序使用大端
意味着:如果是小端机器在传输数据的时候,需要将数据转化为大端字节序进行传输,对端机器默认传输过来的数据是大端字节序
1.发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
2.接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
3.因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
4.TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节. 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据; 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
5.3主机字节序如何转化为网络字节序
5.4网络字节序如何转化为主机字节序
6.UDP编程
6.1sockaddr结构
6.2创建socket文件描述符
6.2.1接口介绍
6.2.2创建套接字的意义
6.3套接字如何与系统进程相关联的
进程启动之后,对应的描述进程的结构体叫做task_struct -> 找到file_stuct -> 找到指针数组 file*fd[] -> 通过创建套接字返回的文件描述符sock在指针数组中拿到指针 -> 找到描述文件的struct file 文件结构体 -> 文件结构体中有个指针private_data ->指向socket结构
6.4绑定端口号
6.4.1为什么需要绑定
创建完套接字之后,对应的文件之中只有文件信息,而我们创建的是网络文件,因此需要填入ip、port,将内存信息和网络关联起来
ip和端口号标识网络之中的唯一进程,可以让客户端找到自己
有了文件信息和网络信息,但是他们之间没有关系,绑定就是让他们之间产生关系
为什么客户端不需要绑定?
大部分服务器是被动的,即服务器绑定了让客户端去寻找,而不是服务器去主动寻找客户端
比如你一段时间没有登录QQ,腾讯服务器不会来询问你为什么不登录
6.4.2接口介绍
6.4.3结构体参数填充
1.填充结构体
2.inet_addr函数
6.5UDP收发接口
6.5.1接收函数
6.5.2发送函数
6.5.3查看网络进程信息
7.代码和实验现象展示
7.1服务端
#pragma once
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/inet.h>
class udpServer
{
private:
string ip;
int port;
int sock;
public:
udpServer(string _ip="127.0.1.1",int _port=7080)
:ip(_ip)
,port(_port)
{}
void initServer()//初始化服务器
{
sock=socket(AF_INET,SOCK_DGRAM,0);
cout<<sock<<endl;
//当前套接字文件里面只有文件信息
//这个文件对应的是网络文件、因此需要加入ip、port信息->绑定
//传入的ip、port是在用户层的,下面需要绑定在内核里面
//因此需要填充结构体,sockaddr_in(ipv4)
struct sockaddr_in local;
local.sin_family=AF_INET;//填充协议
local.sin_port=htons(port);//填充端口->转成网络字节序
local.sin_addr.in_addr::s_addr = inet_addr(ip.c_str());//转换的是字符串、不是类
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
cerr<<"bind error"<<endl;//往显示器上打印,但是对应的文件描述符不一样
exit(1);//绑定失败终止整个进程
}
}
//服务器启动之后、永不退出
//写一个回应服务器
void start()
{
while(1)
{
struct sockaddr_in end_point;//保存是谁送人信息->ip 和port
socklen_t len=sizeof(end_point);//发送人结构体大小
char buff[88];//接收消息缓冲区
buff[0]='\\0';//清空缓冲区
ssize_t size=recvfrom(sock,buff,sizeof(buff)-1,0,(struct sockaddr*)&end_point,&len);//接收消息
if(size>0)//收到消息了
{
buff[size]='\\0';//在末尾添加\\0
cout<<"client send :"<<buff<<endl;
string echo_string = buff;//回显字符串
echo_string+="-> server echo";
sendto(sock,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&end_point,len);
}
}
}
~udpServer()
{
close(sock);//关闭文件描述符
}
};
#include "udpServer.hpp"
int main()
{
udpServer *up=new udpServer();
up->initServer();//初始化
up->start();//运行起来
return 0;
}
7.2客户端
#pragma once
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/inet.h>
class udpClient
{
private:
string ip;
int port;
int sock;
public:
//连接服务器->所以填写server的ip和端口-> 下载软件的时候就是下载客户端
udpClient(string _ip="127.0.1.1",int _port=7080)
:ip(_ip)
,port(_port)
{}
void initClient()//初始化客户端
{
sock=socket(AF_INET,SOCK_DGRAM,0);
cout<<sock<<endl;
//客户端不需要绑定
}
//服务器启动之后、永不退出
//写一个回应服务器
void start()
{
string send_string;
//发送给谁,填充信息
struct sockaddr_in peer;
peer.sin_family=AF_INET;
peer.sin_port=htons(port);
peer.sin_addr.s_addr=inet_addr(ip.c_str());//将字符串转成4字节再转成网络字节序
char buff[88];
while(1)
{
cout<<"please enter your message#"<<endl;
getline(cin,send_string);
sendto(sock,send_string.c_str(),send_string.size(),0,(struct sockaddr*)&peer,sizeof(peer));
buff[0]='\\0';
ssize_t size=recvfrom(sock,buff,sizeof(buff)-1,0,nullptr,nullptr);
if(size>0)
{
buff[size]='\\0';
cout<<"Serve:"<<buff<<endl;
}
}
}
~udpClient()
{
close(sock);//关闭文件描述符
}
};
#include "udpClient.hpp"
int main()
{
udpClient *uc =new udpClient;
uc->initClient();
uc->start();
return 0;
}
7.3实验现象
以上是关于Socket编程的主要内容,如果未能解决你的问题,请参考以下文章