C++编写基础Socket


Scocket的建立的过程就像拨打电话

简单来说需要这些步骤

一:建立服务端(server)

1.创建套接字(socket),和套接字结构体(用于存储IP地址和端口,网络类型等信息)

2.将套接字结构体和套接字描述符(socketfd)绑定(bind)在一起

3.开始监听(listen)

4.开始接收(accept)信息,此时处于阻塞状态,如果没有收到信息将一直阻塞在这里,收到信息则会返回socket的文件描述符。

二:建立客户端(client)

1.创建套接字(socket),和套接字结构体(用于存储目标IP地址和端口,网络类型等信息)

2.开始连接(connet),绑定符合其储存的目标信息的socket。

三:互发数据(recv,send)

经过上面两个步骤,服务端先建立,客户端再连接,这两个socket已经能够通信了。
linux提供了众多API 可以直接读取Sockfd的内容再将其存入到指定的string中。
这里我使用了send(),recv()。互发数据的步骤如下图。

TIPS:这里有一点需要特别注意,如果想要持续的互发数据,那么每次recv()到数据之后,一定要send()回去给另一端。不然另一端不知道这边到底有没有接收完毕,那么还处于send的阻塞状态。
导致客户端只能给服务端发送一次数据,客户端就进入了(send)阻塞状态。这里博主自己踩了坑,研究了两天。


基础的讲完了,然后上代码

server.cpp

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXLINE 1024
int main(int argc,char **argv){
	int listenfd,connfd;//这两个稍后用作socket文件描述符
	struct sockaddr_in sockaddr;//用作储存sock相关信息的结构体
	char buff[MAXLINE];
	int n;
 	char *copy = "copy that";
 	memset(&sockaddr,0,sizeof(sockaddr));//清空sockaddr的内容

	sockaddr.sin_family = AF_INET;//AF_INET代表socket范围是IPV4的网络
 	sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY代表不管数据包目标地址是哪里都接收
 	sockaddr.sin_port = htons(10004);//htons代表将本地字节序(port信息)转化成网络字节序,

 	listenfd = socket(AF_INET,SOCK_STREAM,0);//创建IPV4(AF_INET)的TCP(SOCK_STREAM)的socket

	bind(listenfd,(struct sockaddr *) &sockaddr,sizeof(sockaddr));//将socket与sockaddr绑定

 	listen(listenfd,1024);//开始监听,最长字符串长度1024

	printf("Please wait for the client information\n");
 	if((connfd = accept(listenfd,(struct sockaddr*)NULL,NULL))==-1)//进入接收阻塞状态,直到收到信息
 	{
 		printf("accpet socket error: %s errno :%d\n",strerror(errno),errno);
		exit(0);
 	}

	for(;;){
		 n = recv(connfd,buff,MAXLINE,0);//recvAPI可以获取规定字符长度sockfd的内容到指定buffer
		 buff[n+1] = '\0';//添加文件结束符
		 printf("recv msg from client:%s\n",buff);
		 send(connfd,copy,strlen(copy),0);//将收到的消息发送给客户端,提示这边已经接收完毕
		 memset(buff,0,sizeof(buff));//清空buff内容	 
	}
  	close(connfd);//关闭accept的文件描述符
   	close(listenfd);//关闭socketfd的文件描述符
	return 0;
 }

client.cpp

  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #define MAXLINE 1024
int main(int argc,char **argv)
{
	char *servInetAddr = "127.0.0.1";//127.0.0.1是具有特殊意义的ip地址,代表本地回环,可以用于测试
  	int socketfd;//创建socket文件描述符
  	struct sockaddr_in sockaddr;//创建socket结构体,准备存入相关信息
  	char recvline[MAXLINE], sendline[MAXLINE];//用来存收到的数据,和即将发送出去的数据
  	int n;
 
  	socketfd = socket(AF_INET,SOCK_STREAM,0);//创建IPV4(AF_INET)的TCP(SOCK_STRAM)的socket
  	memset(&sockaddr,0,sizeof(sockaddr));//重置结构体内容
  	sockaddr.sin_family = AF_INET;//作用范围是IPV4的网络
  	sockaddr.sin_port = htons(10004);//将10004转化为网络字节序
  	inet_pton(AF_INET,servInetAddr,&sockaddr.sin_addr);//这个API可以将其中的参数转化为网络字节序,并存入socket结构体中。实际上这里也可以用sockaddr_in.in_addr.sin_addr=htonl(127.0.0.1)来存IP的信息
  
  	if((connect(socketfd,(struct sockaddr*)&sockaddr,sizeof(sockaddr))) < 0 )//开始连接信息匹配的socket
  	{
  		printf("connect error %s errno: %d\n",strerror(errno),errno);
  		exit(0);
  	}

	for(;;){  
		printf("send message to server\n");
 
		fgets(sendline,1024,stdin);//从终端获取用户输入数据
 
  		if((send(socketfd,sendline,strlen(sendline),0)) < 0)//发送给服务端
  		{
  			printf("send mes error: %s errno : %d",strerror(errno),errno);
  			exit(0);
  		}
  		memset(sendline,0,sizeof(sendline));//将sendline内容清空
  		int length = recv(socketfd,recvline,MAXLINE,0);//开始进入接收阻塞状态,直到客户端返回信息,这里接收完毕
  		recvline[length+1] = '\0';
  		printf("%s:receive messag from server:%s\n",__FILE__,recvline);//输出信息到终端
  		memset(recvline,0,sizeof(recvline));//将recvline内容清空
	}
  	close(socketfd);//关闭socket,服务端会收到客户端关闭的信息,也关闭自己的socket。
  	printf("exit\n");
  	exit(0);
}

将上面两个文件编译好,同一台主机客户端和服务端可以通信,如果要在广域网通信,可修改目标IP地址(不过还没试过)。
我没用cout,好吧其实这就是C编写的(ヾ(≧▽≦*)o)

进阶内容(付费内容)

欢迎查看《The Linux Programming Interface》56章~61章。
书的链接在此:http://1.droppdf.com/files/87BCs/the-linux-programming-interface.pdf (需要技术上网,国内的资源我还没找到)

参考的博客,以下两个博客都对结构体成员参数作出了解释,不同的参数对应不同的Socket功能,比如IPV6,UDP发送等等。

: (没有实现持续通信)
:https://www.jianshu.com/p/3b233facd6bb (实现了持续通信,并输出了客户端的IP地址端口等信息)