IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Socket编程(1) -> 正文阅读

[系统运维]Socket编程(1)

Socket编程(1)

编写环境为Windows
不过在Linux略微修改就行了,这个真的不是在做课程实验

实现目标

??实现服务端Server与客户端Client,有客户端向服务端发起通信,服务端能够进行响应。

实现思路

??使用TCP协议进行实现,所以在整个通信流程中,很显然需要让客户端知道怎么与服务端发起通信(也就是怎么找到服务端)。
??服务端需要确定IP地址和端口号,例如本机IP可以用127.0.0.1这类127开头的环回地址(也就是localhost)或者使用ipconfig查询IP地址,以及在服务端运行时的端口号。
??客户端通过socket套接字向服务端的IP和端口建立连接,进行通信。

代码实现

服务端

创建服务端的套接字并返回createsockfd

??默认端口号12138(当然只是我自己写乱填的,只要不是系统保留的端口也别大于65535就可以)。
??WSADATA根据百度百科的描述是(一种用于存储被WSAStartup函数调用后返回的Windows Sockets数据的数据结构)。
??套接字SOCKET变量名为fd。使用SOCKET socket(int af, int type, int protocol)函数建立,第一个参数为地址族,IPV4即AP_INET,第二个参数为协议类型,SOCK_STREAM为流式套接字,第三个参数填写为IPPROTO_TCP使用TCP协议(填0时系统会根据套接字的类型决定应使用的传输层协议,当前仅TCP可选),判断返回值是否合法。
??结构体sockaddr_in和sockaddr定义如下,分别用于存储地址族类型、IP地址、端口号最后一个不使用(用于与结构体sockaddr的大小保持一致),其中端口号和地址需要使用网络字节序(大端法),要注意主机中的字节序是否一致,使用hton()函数进行转换。

struct sockaddr_in {
    ADDRESS_FAMILY sin_family;
    USHORT sin_port;
    IN_ADDR sin_addr;
    CHAR sin_zero[8];
}
struct sockaddr {
    ADDRESS_FAMILY sa_family;           // Address family.
	CHAR sa_data[14];                   // Up to 14 bytes of direct address.
}

??sin_famaily设置为AF_INET,端口号使用htons函数将主机字节序转换为网络字节寻进行存放,地址监听任意网卡也就是全0的地址,可以查看INADDR_ANY的定义。

#define INADDR_ANY (ULONG)0x00000000

??使用bind函数将地址与套接字fd进行绑定,其中第二个参数要使用类型转换否则编译报错。使用listen函数设置fd的监听队列,其中第二个参数大小为TCP三次握手后完成的任务队列大小即accept queue的大小,还有一个队列syn queue为三次握手过程中的任务队列大小,参数在系统中设置。若正确完成则返回fd即可。

SOCKET createsockfd(int port=12138)
{
	WSADATA wd;
	WSAStartup(MAKEWORD(2, 2), &wd);
	SOCKET fd;
	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
	{
		printerror((char*)"建立SOCKET时错误码:");
	}//建立服务端的socket
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(sockaddr_in));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons((unsigned short)port);
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(fd, (struct sockaddr*)&serveraddr, sizeof(sockaddr_in))!=0)
	{
		closesocket(fd);
		printerror((char*)"bind函数时错误码");
	}//socket绑定端口和地址
	if (listen(fd, 23) != 0)
	{
		closesocket(fd);
		printerror((char*)"listen函数时错误码");
	}
	//设置socket监听
	return fd;
}

服务端处理连接 handle

??传入参数为先前建立的套接字fd,buffer为存放数据的空间大小(这个服务端代码目前只能同时处理一个客户端的请求,其余客户端会在三次握手后的accept队列中排队),sockconn为用于与客户端连接的套接字,clientaddr用于存放客户端的相关信息如客户端地址等。
??第一个while(1)为服务端能够持续处理客户端连接。
??调用accept函数从fd监听的accept队列中获取待处理任务的套接字,accept函数当前为阻塞模式,当accept队列为空时会阻塞,后两个参数不需要知道客户端信息时可以填NULL。输出客户端地址,因为是网络字节序,将其转为主机的字节序后,将字符串进行输出。
??第二个while(1)用于与客户端持续通信,recv函数从sockconn套接字中接收数据,第二个参数为缓冲区存放recv接收到的地址,第三个参数为buffer的长度(不是接收信息的长度),第四个参数一般为0,返回值为实际接收到的字节数,接收到内容后判断是否为close,若是则结束并关闭连接,不是则向客户端发送“成功接收”,其中第三个参数与recv中不同,是要发送的数据长度。

void handle(SOCKET fd)
{
	char buffer[1 << 10];
	SOCKET sockconn;
	struct sockaddr_in clientaddr;
	int clientaddrsz=sizeof(sockaddr);
	while (1)
	{
		sockconn = accept(fd, (sockaddr*)&clientaddr, &clientaddrsz);
		printf("客户端地址(%s)\n", inet_ntoa(clientaddr.sin_addr));
		//send(sockconn, buffer, strlen(buffer), 0);
		while (1)
		{
			memset(buffer, 0, sizeof(buffer));
			int state;
			if ((state = recv(sockconn, buffer, sizeof(buffer), 0)) < 0) 
			{
				printf("接收错误,结束连接");
				break;
			}
			if (strcmp("close", buffer) == 0)
			{
				printf("与客户端连接结束");
				break;
			}
			//int receiev = recv(sockconn, buffer, sizeof(buffer) - 1, 0);	 sizeof(buffer) - 1?
			printf("从客户端接收%s\n", buffer);
			strcpy(buffer, "成功接收");
			if ((state = send(sockconn, buffer, strlen(buffer), 0)) < 0)
			{
				printf("发送失败,结束连接");
				perror("send"); 
				break;
			}
			printf("向客户端发送%s\n", buffer);
		}
		closesocket(sockconn);
	}
}

服务端主函数

??如果运行参数给定接口则使用给定的接口,否则默认12138,使用creatsockfd函数建立套接字,调用handle函数开始处理连接,结束时关闭套接字。

int main(int argc,char *argv[])
{
	if (argc != 2)
	{
		fd = createsockfd();
		printf("port number=12138\n");
	}
	else
	{
		fd = createsockfd(atoi(argv[1]));
	}
	handle(fd);
	closesocket(fd);
	return 0;
}

客户端

发起连接createconn

??使用参数给定的ip地址和port端口,向该地址的服务器发起连接请求,调用gethostbyname函数,该函数原型struct hostent *gethostbyname(const char *hostname),用于获取ip地址(函数的参数也可以是域名)对应主机,该函数的返回值为存放主机信息的hostent结构体的内存地址。
??hostent结构体中保存了对应主机的域名,别名,IP地址的地址族,IP长度,及该服务器的可能的多个IP地址,h_addr定义为h_addr_list[0],也就是第一个IP地址。

struct  hostent {
        char    FAR * h_name;           /* official name of host */
        char    FAR * FAR * h_aliases;  /* alias list */
        short   h_addrtype;             /* host address type */
        short   h_length;               /* length of address */
        char    FAR * FAR * h_addr_list; /* list of addresses */
#define h_addr  h_addr_list[0]          /* address, for backward compat */
};

??将host中的IP信息复制到serveraddr中,以及端口号(注意主机字节序和网络字节序的转换)和地址族,使用connect函数向服务端建立连接,第一个参数为套接字,第二个参数为服务端信息,第三个参数为sockaddr长度,若成功建立连接后推出返回fd。

SOCKET createconn(char ip[], int port)
{
	WSADATA wd;
	WSAStartup(MAKEWORD(2, 2), &wd);
	SOCKET fd;
	if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
	{
		printerror((char*)"建立SOCKET时错误码:");
	}//建立客户端的socket
	struct hostent* host;
	if ((host = gethostbyname(ip)) == 0)
	{
		printf("gethostbyname错误\n");
		closesocket(fd);
		exit(0);
	}
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	memcpy(&serveraddr.sin_addr, host->h_addr, host->h_length);
	serveraddr.sin_port = htons((unsigned short)port);
	serveraddr.sin_family = AF_INET;
	//printf("服务端地址(%s)端口号(%d)\n", inet_ntoa(serveraddr.sin_addr));
	if (connect(fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr))==SOCKET_ERROR)
	{
		perror("connect"); 
		closesocket(fd); 
		exit(0);
	}
	return fd;
}

通信过程

??与服务端的通信部分相似,仅发送的数据为用户输入,结束后关闭sockconn的连接。

void handle(SOCKET sockconn)
{
	char buffer[1 << 10];
	printf("最多输入长度1024,输入close结束连接\n");
	while (1)
	{
		int state;
		memset(buffer, 0, sizeof(buffer));
		scanf("%s", buffer);
		if ((state = send(sockconn, buffer, strlen(buffer), 0)) <= 0)
		{
			printf("发送失败,结束连接");
			perror("send");
			break;
		}
		if (strcmp("close", buffer) == 0)
		{
			printf("与服务端连接结束");
			break;
		}
		printf("向服务端发送%s\n", buffer);
		if ((state = recv(sockconn, buffer, sizeof(buffer), 0)) < 0)
		{
			printf("接收错误,结束连接");
			break;
		}
		printf("从服务端接收%s\n", buffer);
	}
	closesocket(sockconn);
}

客户端主函数

??服务端的端口号可以默认,但是客户端发起连接的IP和端口号不能为空,不然不知道找哪个服务端,建立连接后进行通信。

int main(int argc,char* argv[])
{
	if (argc != 3)
	{
		printf("输入地址及端口号");
		return 0;
	}
	SOCKET sockconn=createconn(argv[1], atoi(argv[2]));
	handle(sockconn);
	return 0;
}

总结

??这两份代码是简单的套接字通信,还有很多可以改进的地方,在日后逐一完善。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-11-24 08:22:22  更:2021-11-24 08:23:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 0:19:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码