记录一下SOCKET编程

发布时间 2023-05-24 19:50:19作者: 念秋

记录一下基本的socket编程

首先贴几段代码

centos下的server代码

#include<bits/stdc++.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
using namespace std;

int main()
{
    int server,client;
    struct sockaddr_in serverAddr,clientAddr;

    server = socket(AF_INET,SOCK_STREAM,0); //首先创建套接字
    if(server==-1)
    {
        cout<<"socket error"<<endl;
        return 0;
    }

    memset(&serverAddr,0,sizeof(serverAddr));   //手动清零地址结构体
    serverAddr.sin_family = AF_INET;    //清零后重新设置结构体
    serverAddr.sin_port = htons(9999);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    socklen_t len_sockaddr = sizeof(serverAddr);
    if(bind(server,(struct sockaddr*)&serverAddr,len_sockaddr)==-1) //把套接字和结构体相绑定。为什么这里第3个参数是整形呢,猜想是因为设定第二个参数是大小可变的结构体,所以需要手动告知结构体的大小
    {
        cout<<"bind error"<<endl;
        return 0;
    }

    if(listen(server,5)==-1)    //绑定以后就开始监听端口
    {
        cout<<"listen error"<<endl;
        return 0;
    }

    cout<<"size1 = "<<len_sockaddr<<endl;
    len_sockaddr = 0;
    client = accept(server,(struct sockaddr*)&clientAddr,&len_sockaddr);    //这个服务器接收连接信息,返回值对应抽象的套接字,结构体对应客户的实际地址,第三个参数是指针,猜想是为了保存客户结构体的大小。
    cout<<"size2 = "<<len_sockaddr<<endl;
    if(client==-1)
    {
        cout<<"accept error"<<endl;
        return 0;
    }
    char data[] = "this is server.";
    write(client,data,sizeof(data));    //写数据,和写文件没啥区别
    close(client);
    close(server);
}
//g++ server.cpp -std=c++11

centos下的client代码

#include<bits/stdc++.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
using namespace std;

int main()
{
    int client;
    struct sockaddr_in serverAddr;

    client = socket(AF_INET,SOCK_STREAM,0);
    if(client==-1)
    {
        cout<<"socket error"<<endl;
        return 0;
    }

    memset(&serverAddr,0,sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(9999);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    socklen_t len_sockaddr = sizeof(serverAddr) + 1;
    len_sockaddr = 100;
    //client = connect(client,(struct sockaddr*)&serverAddr,len_sockaddr);
    if(connect(client,(struct sockaddr*)&serverAddr,len_sockaddr)==-1)  //第三个参数是为了告知结构体的大小,至少要大于结构体本来的大小。
    {
        cout<<"connect error"<<endl;
        return 0;
    }
    char data[24];
    read(client,data,sizeof(data));
    cout<<"data = "<<data<<endl;
    close(client);
}
//g++ client.cpp -std=c++11

Win10下的server代码

#include<bits/stdc++.h>

#include<winsock2.h>
using namespace std;

int main()
{
    WSADATA wsaData;
	if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)   //windows下必须首先调用
    {
        cout<<"wsastartup error"<<endl;
        return 0;
    }

    SOCKET server,client;
    sockaddr_in serverAddr,clientAddr;

    server = socket(AF_INET,SOCK_STREAM,0); //首先创建套接字
    if(server==-1)
    {
        cout<<"socket error"<<endl;
        return 0;
    }

    memset(&serverAddr,0,sizeof(serverAddr));   //手动清零地址结构体
    serverAddr.sin_family = AF_INET;    //清零后重新设置结构体
    serverAddr.sin_port = htons(9999);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int len_sockaddr = sizeof(serverAddr);
    if(bind(server,(SOCKADDR*)&serverAddr,len_sockaddr)==-1) //把套接字和结构体相绑定。为什么这里第3个参数是整形呢,猜想是因为设定第二个参数是大小可变的结构体,所以需要手动告知结构体的大小
    {
        cout<<"bind error"<<endl;
        cout<<GetLastError()<<endl;
        return 0;
    }

    if(listen(server,5)==-1)    //绑定以后就开始监听端口
    {
        cout<<"listen error"<<endl;
        return 0;
    }

    cout<<"size1 = "<<len_sockaddr<<endl;
    //len_sockaddr = 0;
    client = accept(server,(SOCKADDR*)&clientAddr,&len_sockaddr);    //这个服务器接收连接信息,返回值对应抽象的套接字,结构体对应客户的实际地址,第三个参数是指针,猜想是为了保存客户结构体的大小。
    cout<<"size2 = "<<len_sockaddr<<endl;
    if(client==-1)
    {
        cout<<"accept error"<<endl;
        return 0;
    }
    char data[] = "this is server.";
    send(client,data,sizeof(data),0);    //写数据,和写文件没啥区别
    closesocket(client);
    closesocket(server);
    WSACleanup();   //windows下结束时必须调用
}


Win10下的client代码

#include<bits/stdc++.h>
#include<winsock2.h>
using namespace std;

int main()
{
    WSADATA wsaData;
	if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)   //windows下必须首先调用
    {
        cout<<"wsastartup error"<<endl;
        return 0;
    }

    SOCKET client;
    sockaddr_in serverAddr;

    client = socket(AF_INET,SOCK_STREAM,0);
    if(client==-1)
    {
        cout<<"socket error"<<endl;
        return 0;
    }

    memset(&serverAddr,0,sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(9999);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int len_sockaddr = sizeof(serverAddr) + 1;
    len_sockaddr = 100;
    //client = connect(client,(struct sockaddr*)&serverAddr,len_sockaddr);
    if(connect(client,(SOCKADDR*)&serverAddr,len_sockaddr)==-1)  //第三个参数是为了告知结构体的大小,至少要大于结构体本来的大小。
    {
        cout<<"connect error"<<endl;
        return 0;
    }
    char data[24];
    recv(client,data,sizeof(data),0);
    cout<<"data = "<<data<<endl;
    closesocket(client);
    WSACleanup();   //windows下结束时必须调用
}

windows下本人用的dev进行编译,没有用VS,不过效果应该是类似的。

下面针对这些API和一些宏定义进行记录

int socket(PF_INET,SOCK_STREAM,0)
win和linux下,返回值都可以认为是整形,只不过win下多了个宏定义。
linux下失败返回-1,win下失败返回INVALID_SOCKET

用计网的概念来解释这个函数:

第一个参数固定为PF_INET(AF_INET),二者等价。即选择IPv4。

image-20230523211224004

第二个参数可以选择SOCK_STREAM/SOCK_DGRAM/SOCK_RAW。一般使用前两个即可,即选择TCP还是UDP(可靠或者不可靠,面向连接或者无连接),最后一个原始套接字可用于自己实现一些协议。
第三个套接字,一般填0即可。当然,你也可以根据自己的需求,选择相应的协议。

image-20230523211637371

总的来说,就是自己设定网络层和运输层的协议。

int bind(sock_serv,(struct sockaddr*)&addr_serv,sizeof(addr_serv))
win和linux还是类似,win下仍然是宏定义,只是win下的SOCKET_ERROR代表出错,实质还是整数-1。
这个函数,用大白话理解:把套接字绑定到具体端口地址,抽象的对象绑定到实体
int listen(sock_serv,5)
第一个参数为服务器的套接字,第二个参数代表最大连接数量
int connect(sock,(struct sockaddr*)&addr_serv,sizeof(addr_serv))
这是客户端才调用的函数,含义为,这个抽象的套接字连接到实体的端口地址上
int accept(sock_serv,(struct sockaddr*)&addr_clnt,&len_sockaddr)
大白话理解:服务器接收一个连接,所以要保存客户的实体地址(结构体)和客户抽象的套接字(返回值)
int write(sock_clnt,message,sizeof(message));
int read(sock,message,sizeof(message)-1);
linux下套接字读写都是和文件一样的操作:写给哪个对象,写什么数据,写多少个数据;从哪儿读,读到哪儿,读多少。
int send(clnt,message,sizeof(message),0)
int recv(sock,message,sizeof(message)-1,0)
win下多了个参数,一般是用不上。其他的类似于linux下。

socket编程,涉及到CPU内存布局,即大小端的区别。可见

大小端的区别

 uint32_t htonl(uint32_t hostlong);   
 uint16_t htons(uint16_t hostshort);   
 uint32_t ntohl(uint32_t netlong);   
 uint16_t ntohs(uint16_t netshort); 
 in_addr_t inet_addr(const char *cp)
h代表host,n代表network,s代表short,l代表long。结合上面的代码,应该很容易理解这几个函数的意思。其中inet_addr就是把字符串格式的ip地址转换为32位的IP地址。

结构体

struct sockaddr_in
 
{
 
	short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/
 
	unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
 
	struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
 
	unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
 
};

linux下:
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };
windows下:
typedef struct in_addr
{
    union{
            struct { unsigned char s_b1,s_b2,s_b3,s_b4; } S_un_b;
            struct { unsigned short s_w1,s_w2; } S_un_w;
            unsigned long S_addr;
    }S_un;
}in_addr;

以下为UDP的一些API

//发送
int FAR sendto (
  IN SOCKET s,
  IN const char FAR * buf,
  IN int len,
  IN int flags,
  IN const struct sockaddr FAR *to,
  IN int tolen
);
//接收
ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socklen_t *fromlen)

image-20230524103818844

int shutdown(int sock,int how);
SHUT_RD:断开输入流。套接字无法接收数据(即使缓冲区收到数据也会被清除),无法调用输入相关函数。
SHUT_WD:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
SHUT_RDWR:同时断开I/O流。相当于分两次调用shutdown(),其中一次以SHUT_RD为参数,另一次以SHUT_WR为参数
Win下第2个参数,略有不同。
SD_RECEIVE 断开输入
SD_SEND 断开输出
SD_BOTH 断开IO

断开连接,使socket变成单向,只能发送或者只能接收。显然,这是有连接的套接字才能使用。

centos下的udpServer代码

#include<bits/stdc++.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
using namespace std;

#define SIZE 64
int main()
{
	int sock_serv,len_str;
	socklen_t len_sockaddr;
	struct sockaddr_in addr_serv,addr_clnt;

	char message[SIZE];
	
	sock_serv = socket(PF_INET,SOCK_DGRAM,0);	//首先创建套接字
	if(sock_serv == -1)
	{
		cout<<"socket error"<<endl;
		return 0;
	}
	
	int port = 8888;
	memset(&addr_serv,0,sizeof(addr_serv));//必须手动清零结构体

	addr_serv.sin_family = AF_INET;	//设置结构体的数据
	addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);
	addr_serv.sin_port = htons(port);
	
	if(bind(sock_serv,(struct sockaddr*)&addr_serv,sizeof(addr_serv))==-1)	//绑定一个套接字
	{
		cout<<"bind error"<<endl;
		return 0;
	}
	
	while(true)
	{
		len_sockaddr = sizeof(addr_clnt);
		len_str = recvfrom(sock_serv,message,SIZE,0,(struct sockaddr*)&addr_clnt,&len_sockaddr);
		cout<<"recv message "<<message<<endl;
		sendto(sock_serv,message,len_str,0,(struct sockaddr*)&addr_clnt,len_sockaddr);

	}
	close(sock_serv);	//关闭套接字
	return 0;
}

centos下udpclient代码

#include<bits/stdc++.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
using namespace std;
#define SIZE 64
int main()
{
    int sock;
    struct sockaddr_in addr_serv,addr_from;
    char message[SIZE];
    int len_read;
    socklen_t len_addr;

    sock = socket(PF_INET,SOCK_DGRAM,0);   //首先创建套接字
    if(sock == -1)
    {
        cout<<"socket error";
        return 0;
    }

    memset(&addr_serv,0,sizeof(addr_serv)); //手动清零结构体
    addr_serv.sin_family = AF_INET; //设置套接字连接的对象
    addr_serv.sin_addr.s_addr = inet_addr("192.168.0.118");
    addr_serv.sin_port=htons(8888);

    while(true)
    {
       fputs("输入数据,输入q退出\n",stdout);
       fgets(message,sizeof(message),stdin);
       if(!strcmp(message,"q\n"))
        {
            break;
        }
        sendto(sock,message,strlen(message),0,(struct sockaddr*)&addr_serv,sizeof(addr_serv));

        len_addr = sizeof(addr_from);

        len_read = recvfrom(sock,message,SIZE,0,(struct sockaddr*)&addr_from,&len_addr);
        message[len_read] = 0;
        cout<<"message from server: "<<message<<endl;

    }
    close(sock);    //关闭套接字
    return 0;
}

win下的udpserver代码

#include<bits/stdc++.h>
#include<winsock2.h>
using namespace std;

#define SIZE 64
int main()
{
	WSADATA wsaData;
	if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)   //windows下必须首先调用
    {
        cout<<"wsastartup error"<<endl;
        return 0;
    }
	int sock_serv,len_str;
	int len_sockaddr;
	struct sockaddr_in addr_serv,addr_clnt;

	char message[SIZE];
	
	sock_serv = socket(PF_INET,SOCK_DGRAM,0);	//首先创建套接字
	if(sock_serv == -1)
	{
		cout<<"socket error"<<endl;
		return 0;
	}
	
	int port = 8888;
	memset(&addr_serv,0,sizeof(addr_serv));//必须手动清零结构体

	addr_serv.sin_family = AF_INET;	//设置结构体的数据
	addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);
	addr_serv.sin_port = htons(port);
	
	if(bind(sock_serv,(struct sockaddr*)&addr_serv,sizeof(addr_serv))==-1)	//绑定一个套接字
	{
		cout<<"bind error"<<endl;
		return 0;
	}
	
	while(true)
	{
		len_sockaddr = sizeof(addr_clnt);
		len_str = recvfrom(sock_serv,message,SIZE,0,(struct sockaddr*)&addr_clnt,&len_sockaddr);
		cout<<"recv message "<<message<<endl;
		sendto(sock_serv,message,len_str,0,(struct sockaddr*)&addr_clnt,len_sockaddr);

	}
	closesocket(sock_serv);	//关闭套接字
	WSACleanup();   //windows下结束时必须调用
	return 0;
}

Win下的udpclient代码

#include<bits/stdc++.h>
#include<winsock2.h>
using namespace std;
#define SIZE 64
int main()
{
    WSADATA wsaData;
	if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)   //windows下必须首先调用
    {
        cout<<"wsastartup error"<<endl;
        return 0;
    }
    int sock;
    struct sockaddr_in addr_serv,addr_from;
    char message[SIZE];
    int len_read;
    int len_addr;

    sock = socket(PF_INET,SOCK_DGRAM,0);   //首先创建套接字
    if(sock == -1)
    {
        cout<<"socket error";
        return 0;
    }

    memset(&addr_serv,0,sizeof(addr_serv)); //手动清零结构体
    addr_serv.sin_family = AF_INET; //设置套接字连接的对象
    addr_serv.sin_addr.s_addr = inet_addr("192.168.0.118");
    addr_serv.sin_port=htons(8888);

    while(true)
    {
       fputs("输入数据,输入q退出\n",stdout);
       fgets(message,sizeof(message),stdin);
       if(!strcmp(message,"q\n"))
        {
            break;
        }
        sendto(sock,message,strlen(message),0,(struct sockaddr*)&addr_serv,sizeof(addr_serv));

        len_addr = sizeof(addr_from);

        len_read = recvfrom(sock,message,SIZE,0,(struct sockaddr*)&addr_from,&len_addr);
        message[len_read] = 0;
        cout<<"message from server: "<<message<<endl;

    }
    closesocket(sock);    //关闭套接字
    WSACleanup();   //windows下结束时必须调用
    return 0;
}

域名解析和IP地址反查域名

#include<bits/stdc++.h>
#include<winsock2.h>	//windows下需要

//linux下需要
//#include<netdb.h>
//#include<arpa/inet.h>

using namespace std;

int main()
{
    WSADATA wsaData;
	if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)   //windows下必须首先调用
    {
        cout<<"wsastartup error"<<endl;
        return 0;
    }

    /*struct hostent  host = *(gethostbyname("www.baidu.com"));   //根据域名获取IP地址
    cout<<host.h_name<<endl;
    cout<<host.h_aliases[0]<<endl;
    cout<<host.h_addrtype<<endl;
    cout<<host.h_length<<endl;
    cout<<inet_ntoa(*(struct in_addr*)host.h_addr_list[0])<<endl;*/
    
    cout<<"after"<<endl;
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    ULONG ip = inet_addr("127.0.0.1");
    
    struct hostent* host1;
    //host1 = gethostbyaddr((char*)&addr.sin_addr,4,AF_INET);
    host1 = gethostbyaddr((char*)&ip,4,AF_INET);    //两种写法可以
    if(!host1)
    {
        cout<<"error"<<endl;
        cout<<GetLastError()<<endl;	//11004,一直返回这个,后来搜索了一下,gethostbyaddr大概只能查询本地hosts文件里面的内容吧
        return 0;
    }
    cout<<host1->h_name<<endl;
    cout<<host1->h_aliases[0]<<endl;
    cout<<host1->h_addrtype<<endl;
    cout<<host1->h_length<<endl;
    cout<<inet_ntoa(*(struct in_addr*)host1->h_addr_list[0])<<endl;

    WSACleanup();   //windows下结束时必须调用
}
//linux和windows相比,也就头文件不太一样。还有win下开头和结尾多了对winsock的调用。
	struct hostent
	{
		char *h_name;         //正式主机名
		char **h_aliases;     //主机别名
		int h_addrtype;       //主机IP地址类型:IPV4-AF_INET
		int h_length;		  //主机IP地址字节长度,对于IPv4是四字节,即32位
		char **h_addr_list;	  //主机的IP地址列表
	};
	
	#define h_addr h_addr_list[0]   //保存的是IP地址
关闭Linux系统的防火墙,否则Windows不能够连接Linux
(1) 重启后永久性生效:   开启:chkconfig iptables on   关闭:chkconfig iptables off   
(2) 即时生效,重启后失效:   开启:service iptables start   关闭:service iptables stop