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(TCP) -> 正文阅读

[网络协议]进程间的通讯:SOCKET(TCP)


前言

socket是在TCP/IP协议上一个种封装,即调用tcp/ip的接口。
socket:可设置为,TCP(流)与UDP(数据报)

UDP:使用无连接协议就像寄信
TCP:使用面向连接的协议就像打电话

在这里插入图片描述


socket系统调用过程(TCP):

	服务端:
	1、socket()创建一个socket
	2、bind()将一个socket绑定到一个地址
	3、listen()允许流socket接受其他socket接入连接
	4、accept()监听流socket上请求的连接
	5、read()或recv() /  wirte()或send()  进行信息交互
	6、close()关闭socket

	客户端:
	1、socket()创建一个socket
	2、connect()与服务端进行连接
	3、 wirte()或send()  / read()或recv() 进行信息交互
	4、close()关闭socket

一、使用的API

头文件:
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

1、socket()创建

int socket(int domain, int type, int protocol);
success:返回文件描述符    error: -1 错误代码存入errno中

domain:  一般把它赋为AF_INET  
		AF_INET:使用IPv4 TCP/IP协议
		AF_INET6:使用IPv6 TCP/IP协议
		AF_UNIX:创建只在本机内进行通信的套接字
type:
		SOCK_STREAM:创建TCP流套接字
		SOCK_DGRAM:创建UDP数据报套接字
		SOCK_RAW:创建原始套接字
protocol:通常设置为0	

2、bind()绑定地址

①、bind()

int bind(int sockfd, const struct sockaddr *addr,
                			socklen_t addrlen);
success:0   error: -1 错误代码存入errno中

sockfd:socket()返回的描述符
addr:指向sockaddr 的的指针,保存着本地套接字的地址(即端口和IP地址)
	 信息。不过由于系统兼容性的问题,一般不使用这个结构,而使用另外一个
	 结构(struct sockaddr_in)来代替
addrlen:sockaddr结构的长度

对于IPV4使用以下结构体:
struct sockaddr_in{
	unsigned short sin_family;	/*地址类型:AF_INET*/
	unsigned short sin_port;	/*端口号*/
	struct in_addr sin_addr;	/*IP地址*/
	unsigned char sin_zero[8];	/*填充字节,一般赋值为0*/
};
struct in_addr {
    uint32_t       s_addr;  /* IP地址网络字节序(大端) */
};
eg:   (struct sockaddr_in srv_addr;)
bzero(&srv_addr,sizeof(struct sockaddr_in));//将内存srv_addr清零
srv_addr.sin_family 	  = 	AF_INET;
srv_addr.sin_addr.s_addr  =	htonl(INADDR_ANY);//本机下的任一个ip
//inet_aton("192.168.136.128",&srv_addr.sin_addr);
srv_addr.sin_port		  =	htons(8000);

INADDR_ANY:让服务器端计算机上的所有网卡的IP地址都可以作为服务器IP地址,也即监听外部客户端程序发送到服务器端所有网卡的网络请求。

如果需要使用特定的IP地址则需要使用inet_addr 和inet_ntoa完成字符串和in_addr结构体的互换

②、网络字节序转换:

整型与整型转换:
uint32_t htonl(uint32_t hostlong);//用于INADDR_ANY
将一个32位数从主机字节顺序转换成网络字节顺序		
				
uint16_t htons(uint16_t hostshort);//用于端口
将一个无符号短整型数值转换为网络字节序

uint32_t ntohl(uint32_t netlong);
将网络字节序转为一个32位数主机字节用于打印显示

uint16_t ntohs(uint16_t netshort);//用于端口打印
将网络字节序转为一个16位数主机字节用于打印显示

字符与整型转换:
int inet_aton(const char *cp, struct in_addr *inp);//首选
将字符串型的ip地址(192.168.136.18)转成网络字节序的sockaddr_in.in_addr

in_addr_t inet_addr(const char *cp);
将字符串型ip转成网络字节序 对255.255.255.255 无效

in_addr_t inet_network(const char *cp);
将字符串型ip转成主机字节序

char* inet_ntoa(struct in_addr inaddr);
将网络字节序转成字符型ip  用于打印

3、listen()转为被动监听

int listen(int sockfd, int backlog);
success:0   error: -1 错误代码存入errno中

sockfd: socket返回的文件描述符
backlog:进入监听队列中允许的最大连接数目。 
		大多数系统的允许数目是20,我们可以设置为510,在还没调用accept
		前,客户端connect请求成功的数量,超出则阻塞,等到调accept进行连
		接

4、accept()等待请求连接

int accept(int sockfd, struct sockaddr *addr,
						 socklen_t *addrlen);
success:返回新的文件描述符用于收发   error: -1 错误代码存入errno中

sockfd: socket返回的文件描述符
addr:用于存储客户端的ip(ip地址、端口)
addrlen:调用前为 addr指向缓冲区大小,调用后为实际被复制缓冲区的大小

5、connect()请求连接服务端

int connect(int sockfd, const struct sockaddr *addr,
                  				 socklen_t addrlen);
success:0   error: -1 错误代码存入errno中

sockfd:客户端socket返回的描述符
addr: 想连接服务端的ip(ip地址和端口 类似bind时的addr)
addrlen:  sizeof(struct sockaddr)

6、send() 发送 与 recv()接收

可阻塞等待和通过fcntl设为非阻塞

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

success: 返回实际发送的数据大小  error:-1,错误原因存于errno

sockfd: 服务端为 accept返回的描述符   客户端为socket返回的描述符
buf: 待发送的数据        和    接收的缓冲区
len: 待发送的字节数     和    接收的最大字节数(如接收超过只会收到max)
flags: 置为 0

7、close()关闭socket

int close(int fd);
success: 0   error:  -1
服务端需要关闭  socket返回的 和 accept返回的
客户端 只要关闭socket返回的

二、案例

1.简单服务端和客户端连接互传信息

stdin输入客户端先发送给服务端,服务端接收后,再stdin输入发送给客户端 如此反复。

tcp_server.c:

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

//服务端收到客户端的连接请求,进入死循环收客户端数据并打印
int main(int argc, char *argv[])//./tcp_server 1234
{
	int skfd,cnfd,addr_len;
	struct sockaddr_in srv_addr,clt_addr;
	int portnumber;
	char buf[128]={0};
	
	if(2 != argc || 0 > (portnumber=atoi(argv[1])))
	{
	     printf("Usage:%s port\n",argv[1]);
	     exit(1);
	}

	/* 创建IPv4的流式套接字描述符 SOCK_STREAM代表TCP协议*/
	if(-1 == (skfd=socket(AF_INET,SOCK_STREAM,0)))   //skfd是监听SOCKET
	{
	     perror("Socket Error:");
	     exit(1);
	}

	/* 填充服务器端sockaddr地址结构 */
	bzero(&srv_addr,sizeof(struct sockaddr_in));
	srv_addr.sin_family=AF_INET;
	inet_aton("192.168.117.128",&srv_addr.sin_addr);//htonl(INADDR_ANY); 本机任意ip
	srv_addr.sin_port=htons(portnumber);//端口0~65535 2个字节

	/* 将套接字描述符skfd和地址信息结构体绑定起来 */
	if(-1 == bind(skfd,(struct sockaddr *)(&srv_addr),sizeof(struct sockaddr)))
	{
	     perror("Bind error:");
	     exit(1);
	}
	printf("bind----%d\r\n",skfd);
	/* 将skfd转换为被动监听模式 */
	if(-1 == listen(skfd,5))
	{
	     perror("Listen error:");
	     exit(1);
	}
	printf("listen----%d\r\n",skfd);
	printf("accept\n"); 
	/* 调用accept,服务器端一直阻塞,直到客户程序与其建立连接成功为止*/
	addr_len=sizeof(struct sockaddr_in);
	if(-1 == (cnfd=accept(skfd,(struct sockaddr *)(&clt_addr),&addr_len))) 
	//cnfd是新连接上来的客户端的链路标识符
	{
		 perror("Accept error:");
		 exit(1);
	}
	printf("accept----%d\r\n",skfd);
	//客户端的IP和PORT
	printf("Connect from %s:%u ...!\n",inet_ntoa(clt_addr.sin_addr),ntohs(clt_addr.sin_port)); 

	while(1)
	{
		memset(buf, 0, sizeof(buf));
		if(-1 == recv(cnfd,buf,sizeof(buf),0)){
			 perror("recv error:");
			 exit(1);
		}
		printf("server recv from client:%s\r\n",buf);


		printf("please input msg to client:");
		memset(buf, 0, sizeof(buf));
		fgets(buf, 4096, stdin); 
		if(-1 == send(cnfd,buf,strlen(buf),0)){
			 perror("Send error:");
			 exit(1);
		}
		if(strncmp(buf, "end",3) == 0)
			 break;
	}

	close(cnfd);
	close(skfd);
	exit(0);
}

tcp_client.c:

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

int main(int argc, char *argv[])//./tcp_client 192.168.117.128 1234
{
    int skfd,ret;
    char buf[128] = {0};
    struct sockaddr_in server_addr;
    int portnumber,nbytes;
    
    if(3 != argc || 0>(portnumber=atoi(argv[2])))
    {
         printf("Usage:%s hostname portnumber \n", argv[0]);
         exit(1);
    }

    /* 创建socket描述符 SOCK_STREAM代表的就是TCP*/
    if(-1 == (skfd=socket(AF_INET,SOCK_STREAM,0)))
    {
         perror("Socket Error:");
         exit(1);
    }
	printf("socket----%d\r\n",skfd);
	
    /* 客户端填充需要连接的服务器的地址信息结构体 */
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(portnumber);//port
    inet_aton(argv[1],&server_addr.sin_addr);//ip
/*	if( inet_pton(AF_INET, argv[1], &srv_addr.sin_addr) <= 0){	
		printf("inet_pton error for %s\n",argv[1]);  
		exit(0);  
		}  */

    while(1)
    {
    	 /* 客户端调用connect主动发起连接请求 数据发给哪个IP+pORT*/
	    if(-1 == connect(skfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)))
	    {
	         perror("Connect Error:");
	         sleep(2);
	    }
	    else
	    {
	    	printf("connect----%d\r\n",skfd);
	    	break;
	    }
    }

    while(1)
    {  	
		memset(buf, 0, sizeof(buf));
		printf("please input msg to server:");
		fgets(buf, 4096, stdin); 
		ret =  send(skfd,buf,strlen(buf),0);
		printf("send:ret=%d, %ld\r\n",ret, strlen(buf));
		
		if(-1 == ret)
		{
			perror("Send error:");
			exit(1);
		}
		if(strncmp(buf, "end",3) == 0)
			break;

		memset(buf, 0, sizeof(buf));
		if(-1 == recv(skfd,buf,sizeof(buf),0))
		{
			perror("recv Error:");
		}
		printf("client read from server:%s\r\n",buf);
		if(strncmp(buf, "end",3) == 0)
			break;
    }

    /* 拆除TCP连接 */
    close(skfd);
    exit(0);
}

在这里插入图片描述

2.服务端可被多个客户端连接(thread)

服务端可被多个客户端连接,当有客户端请求连接时创建一个线程去维护
tcp_server_thread.c:

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

#define CLIENT_MAX 2

int cnfd[CLIENT_MAX];
int cnfd_valid[CLIENT_MAX];

void* thread_client_handle( void *arg )	
{  	
	char buf[32];
	int cnfd_ix;
	int ret;
	
	cnfd_ix = *(int *)arg;
	printf("thread_client_handle=%d\r\n", cnfd_ix);
	
	while(1)
	{
		ret = read(cnfd[cnfd_ix],buf,sizeof(buf));
		printf("server read from client:%s,%d,cnfd_ix=%d\r\n",buf,ret,cnfd_ix);
		if(ret == 0)
		{
			cnfd_valid[cnfd_ix] = 0;
			close(cnfd[cnfd_ix]);		
			break;
		}			
		
		printf("please input msg to client:");
		memset(buf, 0, sizeof(buf));
		fgets(buf,sizeof(buf),stdin);
		if(-1 == write(cnfd[cnfd_ix],buf,strlen(buf)+1)){
			 perror("Send error:");
		}
	}
 	
	return 0;  
} 


int main(int argc, char *argv[])//./tcp_server 1234
{
	int newfd;
	int skfd,addr_len;
	struct sockaddr_in srv_addr,clt_addr;
	int portnumber;
	int ret,i=0;
	pthread_attr_t attr;
	pthread_t th_id0;

	if(2 != argc || 0 > (portnumber=atoi(argv[1])))
	{
	     printf("Usage:%s port\n",argv[1]);
	     exit(1);
	}
	
	pthread_attr_init( &attr );
   	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
   	
	memset(cnfd_valid, 0, sizeof(cnfd_valid));
	
	/* 创建IPv4的流式套接字描述符 SOCK_STREAM代表TCP协议*/
	if(-1 == (skfd=socket(AF_INET,SOCK_STREAM,0)))   //skfd是监听SOCKET
	{
	     perror("Socket Error:");
	     exit(1);
	}

	/* 填充服务器端sockaddr地址结构 */
	bzero(&srv_addr,sizeof(struct sockaddr_in));
	srv_addr.sin_family=AF_INET;
	srv_addr.sin_addr.s_addr= htonl(INADDR_ANY);//inet_addr("219.229.129.206");//IP
	srv_addr.sin_port=htons(portnumber);//端口0~65535 2个字节

	/* 将套接字描述符skfd和地址信息结构体绑定起来 */
	if(-1 == bind(skfd,(struct sockaddr *)(&srv_addr),sizeof(struct sockaddr)))
	{
	     perror("Bind error:");
	     exit(1);
	}
	
	/* 将skfd转换为被动监听模式 */
	if(-1 == listen(skfd,10))
	{
	     perror("Listen error:");
	     exit(1);
	}

	while(1)
	{
		printf("accept\n"); 
		/* 调用accept,服务器端一直阻塞,直到客户程序与其建立连接成功为止*/
		addr_len=sizeof(struct sockaddr_in);
		if(-1 == (newfd=accept(skfd,(struct sockaddr *)(&clt_addr),&addr_len))) 
		//cnfd是新连接上来的客户端的链路标识符
		{
			 perror("Accept error:");
			 exit(1);
		}
		//客户端的IP和PORT
		printf("Connect from %s:%u ...\n",inet_ntoa(clt_addr.sin_addr),ntohs(clt_addr.sin_port)); 
		for(i=0;i<CLIENT_MAX;i++)
		{
			if(cnfd_valid[i] == 0)
			{
				cnfd_valid[i] = 1;
				cnfd[i] = newfd;
				break;		
			}
		}
		if(i>=CLIENT_MAX)
			continue;
		
		pthread_create( &th_id0, &attr, thread_client_handle, &i);
	}

	for(i=0;i<CLIENT_MAX; i++)
	{
		if(cnfd_valid[i] == 1)
		{
			close(cnfd[i]);	
		}
	}	
	
	close(skfd);
	
	exit(0);
}

tcp_client_thread.c:

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

int main(int argc, char *argv[])//./tcp_client 127.0.0.1 1234
{
    int skfd;
    char buf[128] = {0};
    struct sockaddr_in server_addr;
    int portnumber,nbytes;
    
    if(3 != argc || 0>(portnumber=atoi(argv[2])))
    {
         printf("Usage:%s hostname portnumber \n", argv[0]);
         exit(1);
    }

    /* 创建socket描述符 SOCK_STREAM代表的就是TCP*/
    if(-1 == (skfd=socket(AF_INET,SOCK_STREAM,0)))
    {
         perror("Socket Error:");
         exit(1);
    }

    /* 客户端填充需要连接的服务器的地址信息结构体 */
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(portnumber);//port
    inet_aton(argv[1],&server_addr.sin_addr);

    /* 客户端调用connect主动发起连接请求 数据发给哪个IP+pORT*/
    if(-1 == connect(skfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)))
    {
         perror("Connect Error:");
         exit(1);
    }

    while(1)
    {
   		memset(buf, 0, sizeof(buf));
		printf("please input msg to server:");
		scanf("%s",buf);
		if(-1 == write(skfd,buf,strlen(buf)+1)){
			perror("Send error:");
			exit(1);
		}
		if(strcmp(buf, "end") == 0)
			break;	
		
		memset(buf, 0, sizeof(buf));
		if(-1 == read(skfd,buf,sizeof(buf)))
		{
			perror("read Error:");
		}
		printf("client read from server:%s\r\n",buf);
		if(strncmp(buf, "end",3) == 0)
			break;		
    }   

    /* 拆除TCP连接 */
    close(skfd);
    exit(0);
}

在这里插入图片描述

3、服务器可被多个客户端连接(I/O多路复用)

待更新。

进程间的通讯:SOCKET(UDP)


  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-08 11:53:51  更:2021-08-08 11:54:51 
 
开发: 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/25 18:34:18-

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