1 基本概念

socket是操作系统提供的一套标准化网络编程接口,应用程序调用这些接口,可以编写出服务端(Server)和客户端(Client)的socket程序,两端的socket通过特定的IP地址端口连接起来,获得通信能力。

1.1 基本原理

我们可以将socket视作一个功能库,它提供了一套标准的API。
socket库自带了解析器,会对接到对应的操作系统接口。而操作系统的相关部分,就是TCP/IP协议等一些列的网络协议栈。经过协议栈的解析后,就会送到对应的网卡驱动,再之后就是硬件传输了。
在这里插入图片描述
站在TCP/IP五层网络模型的层面,socket,就是传输层向应用层提供的一个使用接口。

2 使用socket

2.1 接口流程

socket底层原理比较复杂,涉及到操作系统和网络通信的各种协议编解码,但使用起来并不是很麻烦。
在这里插入图片描述

2.2 接口定义

下面先详细介绍一下各个接口的使用方法。

2.2.1 socket

socket是创建套接字,返回值是套接字的文件描述符fd

int socket(int af, int type, int protocol);
  • af:地址族(Address Family),常用的有 AF_INET 和 AF_INET6,分别对应 IPv4 和 IPv6。
  • type:传输方式,常用的有 SOCK_STREAM(面向连接的流格式) 和 SOCK_DGRAM(无连接的数据报套接字)。
  • protocol:传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别对应 TCP 和 UDP。
// sockaddr直接使用数组保存数据,可读性差
struct sockaddr{
    sa_family_t  sin_family;   //地址族
    char         sa_data[14];  //IP地址和端口号
};
// sockaddr_in有具体成员变量名,可读性好。两者大小等同
struct sockaddr_in{
    sa_family_t     sin_family;   //地址族
    uint16_t        sin_port;     //16位的端口号
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用
};

2.2.2 bind

bind()是将套接字与特定的 IP 地址和端口绑定。

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
  • sock:套接字文件描述符(由 socket() 创建)
  • addr:套接字地址信息,通常使用 struct sockaddr_in来转换
  • addrlen:addr的长度,直接使用sizeof计算

2.2.3 accept

accept() 用于服务端来接受客户端请求,参数和 bind() 相同

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

返回值为当前连接的文件描述符。

2.2.4 connect

connect() 用于客户端去和服务端建立连接,参数和 bind() 相同。

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);

注意这里sockaddr为服务端的地址信息.

2.2.5 listen

listen() 用于服务端进入监听状态。

int listen(int sock, int backlog);
  • sock:套接字文件描述符。
  • backlog:请求队列的长度。填 SOMAXCONN 表示由系统来决定

2.2.6 recv

recv()用于从客户端文件描述符接收数据。

int recv(int sockid, void* buf, size_t len, int flags);
  • sockid:对于服务端accept函数时,应该使用和当前客户端的文件描述符,不是监听的文件描述符
  • flags:
  • MSG_WAITALL:如果设置了此参数,recv将阻塞到至少有len个字节的数据可用
  • MSG_DONTWAIT: 如果设置了此参数,recv将采用非阻塞的模式,即使没有数据也会立即返回
  • MSG_PEEK:如果设置了此标记,recv将从套接字客户端文件描述符的接收队列读取数据,但不会将其从队列中移除。

2.2.7 其他

剩余的 write()、read()、close() 就是标准的 Linux 调用,句柄就是 socket() 创建的文件描述符,此处不再赘述。

3 代码应用

3.1 服务端代码

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;

int main(int argc ,char *argv[])
{
	if(argc !=2 )
	{
		cout<<"use \"./server port\" to start"<<endl;
	}
	
	// 1.创建服务端socket
	int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(listenfd == -1)
	{
		perror("socket");
		return -1;
	}
	// 2. 将服务端描述符 bind到通信的ip和端口
	sockaddr_in srvAddr = {0};
	srvAddr.sin_family = AF_INET;
	//srvAddr.sin_addr.s_addr = htonl(IPADDR_ANY);
	inet_pton(AF_INET, "127.0.0.1", &srvAddr.sin_addr);
	srvAddr.sin_port = htons(atoi(argv[1]));
	if(bind(listenfd, (sockaddr*)&srvAddr, sizeof(srvAddr)) != 0)
	{
		perror("bind error!");
		close(listenfd);
		return -1;
	}
	// 3.把socket设置为可连接状态
	if(listen(listenfd, 5) != 0)
	{
		perror("listen error!");
		close(listenfd);
		return -1;
	}
	//4. 受理客户端的连接请求,如果没有客户端连上来,accept将一直阻塞
	sockaddr_in clientAddr;
	socklen_t len = sizeof(sockaddr_in);
	int clientfd = accept(listenfd, &clientAddr, &len));
	if(clientfd == -1)
	{
		std::cerr<<"error accept connection"<<std::endl;
		close(listenfd);
		return -1;
	}

	// 5. 与客户端通信
	char buff[1024];
	while(true)
	{
		int iret;
		memset(buff, 0 ,sizoef(buff));
		//阻塞函数,如果客户端已断开,则函数返回0
		iret = recv(clientfd, buffer, sizeof(buff), 0);
		if(iret <= 0)
		{
			cout<<"iret:"<<iret<<endl;
			break}
		cout<<"recv content:"<<buff<<endl;

		strcpy(buffer, "ok");
		if( (iret=send(clientfd, buff, sizeof(buff), 0)) <= 0 )
		{
			std::cout<<"send error"<<endl;
			break;
		}
		cout<<"send:"<<buff<<endl;
	}
	//6.关闭套接字
	close(listenfd);
	close(clientfd);
}

3.2 客户端代码

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <apra/inet.h>
#include <netdb.h>

using namespace std;

int main(int argc ,char *argv[])
{
	if(argc !=3 )
	{
		cout<<"use \"./client serverip port\" to start"<<endl;
	}
	
	// 1.创建客户端socket
	int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sockfd == -1)
	{
		perror("socket");
		return -1;
	}
	// 2. 发起connect连接
	struct hostent* h;
	if((h=gethostbyname(argv[1])) == 0)
	{
		cout<<"get server addr error"<<endl;
		return -1;
	}

	sockaddr_in srvAddr = {0};
	srvAddr.sin_family = AF_INET;
	memcpy(srvAddr.sin_addr, h->h_addr, h->h_length);
	srvAddr.sin_port = htons(atoi(argv[2]));
	if(connect(sockfd, (sockaddr*)&srvAddr, sizeof(srvAddr)) ! = 0)
	{
		perror("connect error!");
		close(sockfd);
		return -1;
	}
	// 3.与服务端通信
	char buff[1024];
	for(int i=0; i< 10; ++i)
	{
		int iret;
		memset(buff, 0 ,sizeof(buff));
		sprintf(buff, "this is the %d girl.", i);
		
		iret = send(sockfd, buff, sizeof(buff), 0);
		if(iret <= 0)
		{
			cout<<"iret:"<<iret<<endl;
			break;
		}
		cout<<"send:"<<buff<<endl;

		memset(buff, 0 ,sizeof(buff));
		if( (iret=recv(sockfd, buff, sizeof(buff), 0)) <= 0 )
		{
			std::cout<<"send error"<<endl;
			break;
		}
		cout<<"recv:"<<buff<<endl;
		sleep(1);
	}
	//4.关闭套接字
	close(sockfd);
}

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部