Linux操作系统 - 网络编程socket
Posted TangguTae
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux操作系统 - 网络编程socket相关的知识,希望对你有一定的参考价值。
目录
socket基本概念
套接字(socket),站在Linux内核的角度来看,就是一个通信的端点。从Linux程序的角度来看,套接字就是一个有相应描述符的打开文件。
在通信的角度来看,大多数通信发生在不同的主机之间,其本质是进程间通信(所看到的公共资源就是网络)。ip地址可以标识全网内唯一的主机,而端口号port可以标识该主机上的唯一的一个进程(这里与进程的pid不一样)
进程pid是每个进程都拥有的,而端口号只有特定的进程(网络进程)才拥有。
pid标定系统内的唯一进程,端口号标识一堆网络进程中的某一个进程。
一个进程只能拥有一个pid,但是一个进程可以用有多个端口号,一个端口号不能被多个进程绑定。
所以可以说,套接字socket = ip + port;
由于套接字也是文件描述符,所以也有一些系统级IO是针对网路通信的
socket编程接口
1、创建套接字
第一个参数是指名通信域,也就是通信协议(协议家族sin_family中的成员)。
具体的有以下这些协议
常用的都是基于TCP/IP协议,所以选择AF_INET(IPV4) 或者AF_INET6(IPV6).
第二个参数是套接字的类型
其中有基于流式的SOCK_STREAM,基于数据报的SOCK_DGRAM。
TCP:面向字节流的可靠传输
UDP:面向数据报的不可靠传输
第三个参数默认设置为0。
返回值
创建成功返回一个文件描述符,失败返回-1 。
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
using namespace std;
int main()
int sock = socket(AF_INET,SOCK_DGRAM,0);//基于IPV4,数据报(UDP)
cout<<sock<<endl;
return 0;
输出为3,前面的0,1,2对应stdin,stdout,stderr。
2、绑定端口号和ip地址
ip地址和端口号绑定在sockaddr_in结构体(针对IPV4)中
sin_family协议类型,上面已经说过,端口号port的大小是16位,ipv4的ip地址为 32位。针对这个结构有一个通用结构,struct sockaddr结构体
会发现接下来的函数bind,connect,accept都是用这个通用结构,因为不同的协议类型他的结构有所不同,只能用通用结构sockaddr,在传参是都需要强制类型转换为sockaddr*。在函数内部通过首部的地址类型来确定到底使用的是哪种结构。
在这里还需要注意一些问题
网络字节序:在网络中的数据都是以大端的方法存放的。
所以需要一批接口将主机序列转化为网络序列。
例如htons,“host to network (short)”,主机序列转化为网络序列,且是short类型的参数
bind函数
用来进行ip和端口号的绑定。绑定的目的是让内核将ip地址和端口号与socketfd进行联系起来(强相关),这个过程客户端和服务器都可以绑定,但是客户端没有必要与socketfd强相关,因为存在大量的客户端,而且客户端一般不会长时间连接服务器。而服务器正好相反,所以只针对服务器。
服务器端的ip和端口号,尤其是端口号不能修改,必须是确定的,众所周知的
一旦服务器端端口号发生改变,客户端就找不到服务器,因为客户端是向服务器发出请求,必须明确服务器的端口号和ip地址,服务器接收到数据时会知道是谁发过来的,见recvfrom函数的参数。除此之外:
1、当客户端进行绑定时,很容易出现冲突,客户端就无法启动
2、客户端虽然也需要唯一性,但是不需要明确必须是哪个端口号,所以可以让操作系统自动去筛选分配
第一个参数就是创建好的套接字socket。
第二个参数就是sockaddr结构体指针。
第三个参数还需要传入结构体的大小。
绑定的过程:
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
class udpServer
private:
string _ip;
int _port;
public:
udpServer(string ip = "127.0.0.1",int port = 8080)
:_ip(ip)
,_port(port)
void initServer()
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0)
cerr<<"sock error!"<<endl;
exit(1);
//进行绑定
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
//inet_addr把点分十进制ip地址字符串转化为32位网络序列
local.sin_addr.s_addr = inet_addr(_ip.c_str());
//需要强制类型转换把sockaddr_in*类型转化为sockaddr*类型
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
cerr<<"bind error!"<<endl;
exit(1);
else
cout<<"bind successfully"<<endl;
;
int main(int argc,char* argv[])//从命令行读取服务器的ip地址和端口号
if(argc != 3)
cerr<<"parameter error!"<<endl;
exit(1);
udpServer *us = new udpServer(argv[1],atoi(argv[2]));
us->initServer();
return 0;
udp服务器初始化结束之后需要进行通信,由于socket本质上是文件描述符,所以还需要两组IO
3、recvfrom函数与sendto函数
recvfrom接收数据
在udp服务器里只讨论recvfrom函数,因为通过recvfrom函数还可以获得远端的地址数据。recv函数在tcp服务器里会经常出现。
recvfrom的第一个参数为socket,第二个参数为接收buffer,第三个为期望读到的长度,第四个参数是当没有数据时是阻塞等待还是非阻塞等待。后面两个参数是输出型参数,关心的是谁发给我的,也就是远端的地址数据。第六个参数一定要初始化,不然出bug。
sendto发送数据
和上面一样udp通信只关心sendto函数,前面几个参数是一样的道理,第五个参数是目的地址参数
写一个启动服务器start函数
void start()
while(true)
char msg[64]='\\0';
struct sockaddr_in remote;//用来保存远端地址
socklen_t len = sizeof(remote);
ssize_t s = recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&remote,&len);
if(s > 0)
msg[s] = '\\0';
cout<<"get msg from Client: "<<msg<<endl;
string echo_string = msg;
echo_string+="[server echo]";
//收到数据后并返回一个消息
sendto(sock,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&remote,len);
用netstat查看当前操作系统的网络信息 -nlup中的n表示以数字的形式显示,u表示udp通信,p表示进程。发现当前网络多了一个ip地址为127.0.0.1,端口为8080的网络进程。
简易版的udp服务器大概就是这样,现在来看看客户端
客户端首先不需要进行bind操作,其他操作和服务器是一样的
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
//客户端
class udpClient
private:
string _ip;
int _port;
int sock;
public:
//需要服务器的ip地址和端口号
udpClient(string ip = "127.0.0.1",int port = 8080)
:_ip(ip)
,_port(port)
void initClient()
sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0)
cerr<<"sock error!"<<endl;
exit(1);
//不需要绑定
void start()
//服务器的ip地址和端口号填入sockaddr结构中
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(_port);
remote.sin_addr.s_addr = inet_addr(_ip.c_str());
while(true)
string msg;
cout<<"please enter msg# ";
cin>>msg;
sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&remote,sizeof(remote));
char echo[128] = '\\0';
//最后两个参数置空,因为已经知道了服务器的ip地址和端口号
ssize_t s = recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);
if(s > 0)
echo[s] = '\\0';
cout<<"get msg from Server: "<<echo<<endl;
;
int main(int argc,char* argv[])
if(argc != 3)
cerr<<"parameter error!"<<endl;
exit(1);
udpClient *uc = new udpClient(argv[1],atoi(argv[2]));
uc->initClient();
uc->start();
return 0;
客户端需要知道服务器的ip地址和端口号才能知道我要向谁通信。
其实在服务器这边,不一定需要指定的ip地址,可以在s_addr里面填入INADDR_ANY这个选项,表示该主机上的任意ip地址来的数据都可以收到。
上面的小程序并没有连接,监听等操作,同样也说明udp是面向无连接的。
以上是关于Linux操作系统 - 网络编程socket的主要内容,如果未能解决你的问题,请参考以下文章