欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Socket网络编程基础

程序员文章站 2022-07-11 11:46:57
...

Socket网络编程

部分内容参考于韦东山团队编写的《IMX6ULL应用编程电子书初稿(500页)》

1 为什么要进行网络编程

socket网络编程是通过网络来进行通信,可以使数据的传输距离远,可靠性强。(关于socket网络编程的目的本人还是未能通透的理解其中的内涵,如果有读到本篇博文的读者对socket网络编程有自己的理解,可以的话还请在评论区留言或者私信我,本人将感激不尽????

网络编程分为客户端(Client)编程和服务端(Server)编程;客户端是主动地发起请求,服务器端是被动响应请求。

2 常用网络编程的种类

在Socket网络编程中常用的是TCP编程和UDP编程

2.1 TCP

传输控制协议(Transfer Control Protocol)是一种面向连接的协议,使用它可保在证客户端和服务端之间的连接是安全的、可靠的;TCP在传输数据之前会在两个端点(套接字/插口)之间建立一条虚拟电路,它用于传输对数据的可靠性要求较高的数据。

2.2 UDP

用户数据报协议(User Datagram Protocol)是一种非面向连接的协议,它不保证客户端和服务端之间的连接是安全的、可靠的;UDP是尽最大努力来交付数据的并且没有拥塞控制,特别适合于用来传输数据量大且同时传输过程中丢失的部分数据对用户无太的影响数据。

3 网络编程的流程

3.1 面向连接的TCP流模式

  1. 服务端监听端口号(listen)
  2. 客户端请求连接(connect)
  3. 服务端确认连接(accept)

图片截自韦东山团队编写的《IMX6ULL应用编程电子书初稿(500页)》

Socket网络编程基础

3.2 UDP用户数据包模式

图片截自韦东山团队编写的《IMX6ULL应用编程电子书初稿(500页)》

Socket网络编程基础

4 网络编程常用函数解析

/* 需包含的头文件 */
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

4.1 socket

int socket(int domain, int type, int protocol);	//函数原型

此函数用于创间一个套接字(传输数据的端点)。

domain 是网络程序所在的主机所采用的通信协议族( AF_UNIX / AF_LOCAL和 AF_INET等 ,可通过man socket查看)。AF_UNIX只能够用于单一的Unix 系统进程间通信,而AF_INET是针对IPv4 Internet protocols 的,因而可以允许在远程;这里一般使用AF_INET。

type 是网络程序所采用的通讯协议(SOCK_STREAM和SOCK_DGRAM 等)。SOCK_STREAM表明所用的是TCP协议,而SOCK_DGRAM则表明所用的是UDP协议。

关于指定了protocol,因为指定了type,所以这里一般添0。

socket函数成功返回文件描述符,失败则返回 -1。

4.2 bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);	//函数原型

此函数用于将之前创建的套接字和一个地址绑定起来。(服务器使用此函数)

sockfd 为创建socket时返回的文件描述符。

addr 是sockaddr结构体变量的地址。

addrlen 是结构体sockaddr的长度。

//由于系统的兼容性,我们一般不用sockaddr结构体,而使用另外一个结构(struct sockaddr_in) 来代替
struct sockaddr_in{
    unsigned short	sin_family;	//通讯协议族,一般为 AF_INET
    unsigned short	sin_port;	//要监听的端口号	
    struct in_addr	sin_addr;	//设置为INADDR_ANY表示可以和任何的主机通信
    unsigned char	sin_zero[8];//保留数据,以保证该结构体大小结构体sockaddr相同;要清零。
};

/* Internet address. */
struct in_addr {
	__be32	s_addr;
};

bind将要监听的本地端口和socket返回的文件描述符捆绑在一起。成功返回0,失败则返回 -1。

4.3 listen

int listen(int sockfd, int backlog);	//函数原型

此函数用于宣告服务器可以监听连接请求。(服务器使用此函数)

sockfd 是bind后的文件描述符。

backlog 设置允许连接队列的最大长度,当队列满后新请求连接的客户端将连接不上服务器。

listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样。

4.4 accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	//函数原型

服务器使用此函数来确定是否接受连接请求,允许接受则建立连接,否则拒绝连接。

sockfd 是listen后的文件描述符。

addr 存放所连上客户端的相关信息(IP地址等)。

addrlen 是struct sockaddr的长度。

调用accept时,服务器端的程序会一直阻塞到有一个客户程序发出了连接.,accept成功时返回最后的服务器端的文件描述符,之后服务器端可以用该描述符和客户端程序进行通信,失败则返回 -1。

4.5 connect

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);	//函数原型

该函数是TCP用来建立连接的(客户端使用此函数)。

sockfd 是客户端调用socket函数时返回的文件描述符,用来同服务器端通讯。

addr 存放的是服务器端的连接信息(通讯协议族和端口号等)。

addrlen 是struct sockaddr的长度。

connect函数是客户端用来同服务器端建立连接的,成功是返回0,失败则返回 -1。

4.6 send函数

ssize_t send(int sockfd, const void *buf, size_t len, int flags);	//函数原型

该函数是TCP用来发送数据的。

sockfd 是发送端的套接字描述符。

buf 是要发送的数据所在的地址。

len 是实际要发送数据的长度。

flags 一般为0。

该函数成功时返回成功发送数据的字节数,失败则返回 -1。

4.7 recv函数

ssize_t recv(int sockfd, void *buf, size_t len, int flags);	//函数原型

该函数是TCP用来接收数据的。

sockfd 是接收端的套接字描述符。

buf 是存放所接收数据的地址。

len 是buf的长度。

flags 一般为0。

该函数成功时返回成功接收数据的字节数,失败则返回 -1。

4.8 sendto函数

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);	//函数原型

该函数是UDP用来发送数据的。

参数sockfd、buf、len、flag和send函数中的参数相同;

dest_addr 是目的端的sockaddr结构体变量地址。

addrlen 是struct sockaddr的长度

该函数成功时返回成功发送数据的字节数,失败则返回 -1。

4.9 recvfrom函数

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);	//函数原型

该函数是UDP用来接收数据的。

参数sockfd、buf、len、flag和recv函数中的参数相同;

src_addr 是源端的sockaddr结构体变量地址。

addrlen 是struct sockaddr的长度

该函数成功时返回成功接收数据的字节数,失败则返回 -1。

5 TCP编程实例

5.1 Server端程序

#include <sys/types.h>		/* 网络编程所需头文件*/
#include <sys/socket.h>		/* 网络编程所需头文件*/
#include <string.h>
#include <netinet/in.h>		/* 包含类似inet_ntoa函数的头文件 */
#include <arpa/inet.h>		/* 包含类似inet_ntoa函数的头文件 */
#include <unistd.h>			/* 包含close函数、forck函数等系统调用函数的头文件 */
#include <stdio.h>			
#include <signal.h>			/* 包含signal函数的头文件 */

#define SERVER_PORT 8888 	/* 要监听的端口号 */
#define BACKLOG		 10		/* listen函数中允许请求连接的最大个数 */

/* socket
 * bind
 * listen
 * accept
 * send/recv
 * 用法: ./server
 */
int main(int argc, char **argv)
{
	int iSocketServer;
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;		/* 存放服务器端的通讯协议族、要监听的端口号等信息的结构体 */
	struct sockaddr_in tSocketClientAddr;		/* 存放连接的客户端的IP地址等信息的结构体 */
	int iRet;
	int iAddrLen;
	
	int iRecvLen;
	unsigned char ucRecvBuf[1000];

	int iClientNum = -1;

	signal(SIGCHLD, SIG_IGN);	/* 处理僵死进程,如果不加这句的话,被关闭的客户端所创建的服务器端子进程的资源将不会被父进程回收从而变为僵死进程,将导致资源的浪费 */

	iSocketServer = socket(AF_INET, SOCK_STREAM, 0);	/* 创建socket */
	if (iSocketServer == -1)
	{
		printf("Socket error!\n");
		return -1;
	}

	tSocketServerAddr.sin_family 		= AF_INET;
	tSocketServerAddr.sin_port	 		= htons(SERVER_PORT);	/* 将short型的端口数据转换成适合于网络传输的数据类型 */
	tSocketServerAddr.sin_addr.s_addr 	= INADDR_ANY;	/* 允许和任何的主机通信 */
	memset(tSocketServerAddr.sin_zero, 0, 8);			/* 内存置0,确保和struct sockaddr的长度相同 */

	iRet =  bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	 /* 之前创建的socket文件描述符将被bind修饰 */
	if (iRet == -1)
	{
		printf("Bind error!\n");
		return -1;
	}

	iRet = listen(iSocketServer, BACKLOG);	/* bind修饰后的socket文件描述符又将被listen修饰 */
	if (iRet == -1)
	{
		printf("Listen error!\n");
		return -1;
	}

	while (1)
	{
		iAddrLen = sizeof(struct sockaddr);
		iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);	/* accept成功返回客户端的socket文件描述符,服务端使用该文件描述符来与客户端进行通信 */
		if (iSocketClient != -1)
		{
			iClientNum++;
			printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));	/* inet_ntoa()将网络IP地址转换为字符串 */
			if (!fork())	/* 创建子进程,fork()将同时返回两个值,当fork()返回0时执行子进程的程序,当fork()返回大于0的值时执行父进程的程序且返回的值是子进程的PID */	
			{
				/* 子进程源码 */
				while (1)
				{
					/* 接收客户端发来的数据并显示出来 */
					iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
					if (iRecvLen <= 0)
					{
						close(iSocketClient);	/* 无数据传输或接收失败时关闭建立的客户端socket文件描述符 */
						return -1;
					}
					else
					{
						ucRecvBuf[iRecvLen] = '\0';		/* 为接收的字符串添加结尾符 */
						printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
					}
				}
			}
		}
	}
	close(iSocketServer);	/* 退出服务器端进程时关闭服务器端建立的socket文件描述符 */
	
	return 0;
}

5.2 Client端程序

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

#define SERVER_PORT 8888

/* socket
 * connect
 * send/recv
 * 用法: ./client 192.168.1.1
 */
int main(int argc, char **argv)
{
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	
	int iRet;
	unsigned char ucSendBuf[1000];
	int iSendLen;

	if (argc != 2)
	{
		printf("Usage:\n");
		printf("%s <server_ip>\n", argv[0]);
		return -1;
	}

	iSocketClient = socket(AF_INET, SOCK_STREAM, 0);

	tSocketServerAddr.sin_family 		= AF_INET;
	tSocketServerAddr.sin_port	 		= htons(SERVER_PORT);	/* host to net, short */
	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	if (inet_aton(argv[1], &tSocketServerAddr.sin_addr) == 0)	/* inet_aton()函数将输入的字符串类型的IP地址转换为网络IP地址,返回为 0 则代表无效的IP地址 */
	{
		printf("invaild server_ip\n");
		return -1;
	}
	memset(tSocketServerAddr.sin_zero, 0, 8);

	iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
	if (iRet == -1)
	{
		printf("connect error\n");
		return -1;
	}

	while (1)
	{
		if (fgets(ucSendBuf, 999, stdin))	/* fgets()函数将输入的字符存入自定义的缓冲区中,stdin表示从标准输入得到字符 */
		{
			iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
			if (iSendLen <= 0)
			{
				close(iSocketClient);
				return -1;
			}
		}
	}

	return 0;
}

6 UDP编程实例

6.1 Server端程序

#include <sys/types.h>       
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

#define SERVER_PORT 8888
#define BACKLOG		 10

/* socket -- 得到socket句柄 
 * bind -- 绑定检测的ip和端口
 * recvfrom -- 接收数据
 * 用法: ./server
 */
int main(int argc, char **argv)
{
	int iSocketServer;
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	struct sockaddr_in tSocketClientAddr;
	int iRet;
	int iAddrLen;
	
	int iRecvLen;
	unsigned char ucRecvBuf[1000];

	int iClientNum = -1;

	iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);		/* SOCK_DGRAM表示 UDP 协议 */
	if (iSocketServer == -1)
	{
		printf("Socket error!\n");
		return -1;
	}

	tSocketServerAddr.sin_family 		= AF_INET;
	tSocketServerAddr.sin_port	 		= htons(SERVER_PORT);	/* host to net, short */
	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	memset(tSocketServerAddr.sin_zero, 0, 8);

	iRet =  bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
	if (iRet == -1)
	{
		printf("Bind error!\n");
		return -1;
	}

	while (1)
	{
		iAddrLen = sizeof(struct sockaddr);
		iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);	/* recvfrom()函数中要传入源端的IP地址等信息 */
		if (iRecvLen > 0)	
		{
			ucRecvBuf[iRecvLen] = '\0';
			printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
		}

	}
	close(iSocketServer);
	
	return 0;
}

6.2 Client端程序

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

#define SERVER_PORT 8888

/* socket
 * sendto/recvfrom
 * 用法: ./client 192.168.1.1
 */
int main(int argc, char **argv)
{
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	
	int iRet;
	unsigned char ucSendBuf[1000];
	int iSendLen;
	int iAddrLen;

	if (argc != 2)
	{
		printf("Usage:\n");
		printf("%s <server_ip>\n", argv[0]);
		return -1;
	}

	iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);

	tSocketServerAddr.sin_family 		= AF_INET;
	tSocketServerAddr.sin_port	 		= htons(SERVER_PORT);	/* host to net, short */
	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	if (inet_aton(argv[1], &tSocketServerAddr.sin_addr) == 0)
	{
		printf("invaild server_ip\n");
		return -1;
	}
	memset(tSocketServerAddr.sin_zero, 0, 8);
#if 0
	iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
	if (iRet == -1)
	{
		printf("connect error\n");
		return -1;
	}
#endif
	while (1)
	{
		if (fgets(ucSendBuf, 999, stdin))
		{
			iAddrLen = sizeof(struct sockaddr);
			iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0, (const struct sockaddr *)&tSocketServerAddr, iAddrLen);	/* sendto()函数中需要传入目的端的IP地址等信息 */
			if (iSendLen <= 0)
			{
				close(iSocketClient);
				return -1;
			}
		}
	}

	return 0;
}