#include
//将主机字节序转换为网络字节序
unit32_t htonl (unit32_t hostlong);
unit16_t htons (unit16_t hostshort);
//将网络字节序转换为主机字节序
unit32_t ntohl (unit32_t netlong);
unit16_t ntohs (unit16_t netshort);
说明:h -----host;n----network ;s------short;l----long。
htons()--"Host to Network Short"
htonl()--"Host to Network Long"
ntohs()--"Network to Host Short"
ntohl()--"Network to Host Long"
————————————————
一个IP地址加一个端口号可以标识网络中某个主机上一个进程,因此网络也是进程间通信的一种,网络就是那份公共资源。操作系统提供了socket API的这样一层抽象的网络编程接口,有对应的属性和方法来完成两个不同主机的进程间的通信。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。
套接字是一套用C语言写成的应用程序开发库,它首先是一个库。主要作用就是实现进程间通信和网络编程,因此在网络应用开发中被广泛使用。套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序与网络中的其他应用程序进行通信。**网络套接字是IP地址与端口的组合。**实际开发中使用的套接字可以分为三类:流套接字(TCP套接字(SOCK-STREAM))、数据报套接字(SOCK_DGRAM)和原始套接字(SOCK-RAW)。
数据报套接字(SOCK_DGRAM)提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP( User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
流套接字(SOCK_STREAM)用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了TCP(The Transmission Control Protocol)协议传输控制协议。
原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接。
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
struct sockaddr 这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。
sockaddr结构
:
netinet/in.h
中,IPv4
地址用sockaddr_in结构体
表示,包括16位地址类型, 16位端口号和32位IP地址。AF_INET
、AF_INET6
。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。struct sockaddr *
类型表示,在使用的时候需要强制转化成struct sockaddr *
,这样的好处是程序的通用性,可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。sockaddr_in结构体
:有些内容需要自己填充。truct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
sin_family:指代协议族,在socket编程中只能是AF_INET
sin_port:存储端口号(使用网络字节顺序)
sin_addr:存储IP地址,使用in_addr这个数据结构
sin_zero:是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
问:所有的接口都是struct sockaddr
类型,如何区分对于具体的传入使用哪个结构体?
答案是:提取第一个字段进行if判断,如果为AF_INET
则为struct sockaddr_in
。而且每个结构的大小不同,所以还需要传入用户层实际结构体的长度进行处理。如此一来就达到同一套接口,传入参数的不同,进行不同的函数操作,达到了静态多态——函数重载。
问:为什么在数据结构 struct sockaddr_in
中, sin_addr
和 sin_port
需要转换为网络字节顺序,而sin_family
需不需要呢?
答案是: sin_addr
和 sin_port
分别封装在包的 IP 和 UDP 层。因此,它们必须要 是网络字节顺序。但是 sin_family
域只是被内核 (kernel) 使用来决定在数 据结构中包含什么类型的地址,所以它必须是本机字节顺序。同时, sin_family 没有发送到网络上,它们可以是本机字节顺序。
数据报套接字使用UDP( User Datagram Protocol)协议进行数据的传输,提供一种无连接的服务,该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。实现起来比较简单。
UDP服务端:利用系统提供的socket接口创建套接字,返回一个文件描述符;然后将主机IP和端口号绑定至特定的网络文件;接着因为服务器是一个软件 程序,周而复始一直在运行,永远不退出,所以死循环接受数据,处理数据,返回数据;最后关闭套接字文件。
UDP客户端:创建套接字网络文件;不需要绑定,因为客户端不是必须哪一个端口,只要有一个端口就行,由操作系统自动分配;然后发送数据,接受服务端返回的数据;最后关闭套接字避免文件描述符泄露。为什么服务器端需要绑定?服务器是向外提供服务的,需要尽可能地将自己的IP暴露出去,端口号一般隐藏;这两者必须是稳定的,尤其是端口号,本服务进程独有。
int socket(int domain, int type, int protocol);
domain
: AF_INET IPv4 Internet protocols
,使用哪种格式的IP协议。type
:使用传输层的哪种协议。TCP: SOCK_STREAM
,UDP:SOCK_DGRAM
protocol
:默认为0。协议指定与套接字一起使用的特定协议,通常只有一个协议支持给定协议族中的特定套接字类型,在这种情况下,协议可以指定为0。 #include
#include
int sock = socket(AF_INET, SOCK_DGRAM, 0); //用户数据报 ipv4 udp
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
socket
:创建的文件描述符const struct sockaddr *address
:输入型参数;IPV4格式的需要传入 struct sockaddr_in
,然后强转;
sockaddr_in
:里面有些内容需要自己填充。里面的端口号要转网络字节序,IP地址也要转。INADDR_ANY
, bind所有你的机器上面的ip。socklen_t address_len
:输入性参数;上面该结构体的大小。[0-255].[0-255].[0-255].[0-255]
,“42.192.83.143”,是点分十进制字符串风格的IP。#define PORT 8081
//该结构是OS给你提供的一个结构体,用户层定义的,local是属于main函数内的一个临时变量
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET; //使用的IP类型
local.sin_port = htons(PORT); //后续网络端口,会以源端口的方式,发送给对面
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr << "bind error" << std::endl;
return 3;
}
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
sockfd
:对应操作的套接字文件描述符,表示从该文件描述符索引的文件当中读取数据buf
:读取数据的存放位位置len
:期望读取数据的字节数flags
:读取的方式,一般设置为0,表示阻塞读取。src_addr
:输入输出型参数;保存对端网络相关的属性信息,包括协议家族、IP地址、端口号等。addrlen
:输入输出型参数;上述结构体的长度。 char message[1024];
for( ; ; ){
memset(message, 0, sizeof(message));
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(sock, message, sizeof(message)-1, 0, (struct sockaddr*)&peer, &len);
if(s > 0)
{
//处理.....
}
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
:传入dest_addr结构体的长度...
if(s > 0)
{
//处理.....
message[s]='\0';
std::cout<<"cilent: "<<message<<std::endl;
std::string echo_mess = message;
echo_mess += " _server_";
sendto(sock_fd, echo_mess.c_str(),echo_mess.size(), 0, (struct sockaddr*)&peer,len);
}
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string proc)
{
std::cerr << "Usage: " << "\n\t" << proc << " local_port" << std::endl;
}
// ./udp_server port
int main(int argc, char *argv[])
{
if(argc != 2){
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET, SOCK_DGRAM, 0); //用户数据报
if(sock < 0){
std::cerr << "socket error" << std::endl;
return 2;
}
std::cout <<"sock: " << sock << std::endl;
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr << "bind error" << std::endl;
return 3;
}
//服务器是一个软件 程序,周而复始一直在运行,永远不退出
char message[1024];
for( ; ; )
{
memset(message, 0, sizeof(message));
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(sock, message, sizeof(message)-1, 0, (struct sockaddr*)&peer, &len);
if(s > 0) //对客户端的数据简单的原样返回。
{
message[s] = '\0';
std::cout << "client# " << message << std::endl;
std::string echo_message = message;
echo_message += "_server_";
echo_message += std::to_string((long long)time(nullptr));
sendto(sock_fd, echo_mess.c_str(),echo_mess.size(), 0, (struct sockaddr*)&peer,len);
}
}
close(sock);
return 0;
}
[saul@VM-12-7-centos tt815]$ ./udp_server 8081
makefile
文件:.PHONY:all
all:udp_client udp_server
udp_client:udp_client.cc
g++ -o $@ $^ -std=c++11 -static
udp_server:udp_server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f udp_client udp_server
#include
#include
#include
#include
#include
#include
#include
#include
// ./udp_client 127.0.0.1 8081
void Usage(std::string proc) //提示使用方式不对
{
std::cerr << "Usage: " << "\n\t" << proc << " desc_ip desc_port" << std::endl;
}
int main(int argc, char *argv[])
{
if( argc != 3 ){
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 0){
std::cerr << "socket error" << std::endl;
return 2;
}
char buffer[1024];
struct sockaddr_in desc;
memset(&desc, 0, sizeof(desc));
desc.sin_family = AF_INET;
desc.sin_port = htons(atoi(argv[2]));
desc.sin_addr.s_addr = inet_addr(argv[1]);
for( ; ; ){
std::cout << "please enter ";
fflush(stdout);
buffer[0] = 0;
ssize_t size = read(0, buffer, sizeof(buffer)-1); //从标准输入读取数据
if(size > 0){
buffer[size - 1] = 0;
sendto(sock, buffer, strlen(buffer), 0, (struct sockaddr*)&desc/*发送到哪里??*/, sizeof(desc)/*长度*/);
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len); //peer, len,暂时不用
if(s > 0){
buffer[s] = 0;
std::cout << "echo# " << buffer << std::endl;
}
}
}
close(sock);
return 0;
}
测试:编译后,先运行服务器端,用 netstat //查看网络状态 -nlup 查看UDP进程
;再运行客户端,ctrl c结束进程。
客户端填IP时,地址用:127.0.0.1
测试,本地环回IP。
.....
if(s > 0)
{
char *command[64] = {0};
command[0] = strtok(message, " ");
int i = 1;
while(command[i] = strtok(NULL, " "))
{
i++;
}
if(fork() == 0)
{//child
execvp(command[0], command);
std::cerr << "client message# " << command << std::endl;
exit(4);
}
sendto(sock, echo_message.c_str(), echo_message.size(), 0, (struct sockaddr*)&peer, len);
}
FILE *popen(const char *command, const char *type);
command
:可以执行任何命令;后台自动创建子进程去执行命令,通过文件指针的方式返回,文件中保存的是执行结果。char* type
:打开文件的方式。....
if(s > 0){
//command,命令行命令 , ls -a -l -i
FILE *in = popen(message, "r");
if(in == nullptr)
{
continue;
}
std::string echo_message;
char line[128];
while(fgets(line, sizeof(line), in))
{
echo_message += line;
}
sendto(sock, echo_message.c_str(), echo_message.size(), 0, (struct sockaddr*)&peer, len);
}