Socket通信

1、什么是套接字

Socket是封装了TCP/IP协议簇的系统API接口,这使得程序员无需关注协议本身,直接使用socket提供的接口与不同主机间的进程互联通信。

目前市面上主流的操作系统都采用这套机制进制网络通信,所以不同种类的操作系统,使用不同的编程语言,只要调用操作系统提供的Socket接口,都能进行网络通信。

2、基于TCP协议的Socket编程模型
被连接者需要完成的任务(服务端):
1、创建socket内核对象,内核创建完成后会返回它的描述符(该描述只是为了完成连接,三次握手)
2、准备本机地址(ip地址+端口号)
3、绑定(把本机地址与socket对象进行绑定)
4、开启监听,并设置排队的队列长度
5、等待连接,连接成功后,内核会再返回一个连接成功的Socket描述符,专门用来通信
for(;;)
{
    6、接收请求
    7、返回结果
}
8、关闭通信的Socket对象
9、关闭连接的Socket对象
​
连接者需要完成的任务(客户端):
1、创建socket内核对象,内核创建完成后会返回它的描述符
2、准备被连接者的地址(ip地址+端口号)
3、发起连接,使用Socket+地址(ip地址+端口号)发起连接请求
for(;;)
{
    4、发送请求
    5、接收结果
}
6、关闭Socket对象

3、TCP通信需要使用Socket接口
int socket(int domain, int type, int protocol);
功能:创建Socket对象
domain:
    AF_UNIX, AF_LOCAL   采用本地socket文件进行通信,如果用它则只能本机上的两个进程进行通信
    AF_INET             IPv4地址
    AF_INET6            IPv6地址
type:
    SOCK_STREAM 数据流 TCP
    SOCK_DGRAM 报文 UDP
protocol:
    特殊通信协议,写0即可
返回值:
    成功则返回Socket对象描述符,失败返回-1。
​
// 基本地址类型,它是socket系列接口的表面参数,而实际使用的是sockaddr_un或sockaddr_in,我们需要把sockaddr_in强制转换成sockaddr类型。
struct sockaddr_in 
{
    sa_family_t sin_family;     // 地址类型,与domain保持一致即可
    in_port_t sin_port;         // 端口号,网络字节序的2字节整数
    struct in_addr sin_addr.s_addr; // IP地址,网络字节序的4字节整数
};
​
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:绑定Socket对象与通信地址
sockfd:Socket对象描述符
addr:通信地址,实际提供可能是sockaddr_un或sockaddr_in,需要对它们进行强制转换
addrlen:addr结构体的字节数
返回值:成功返回0,失败返回-1
    
int listen(int sockfd, int backlog);
功能:开启Socket对象的监听
sockfd:Socket地址描述符
backlog:备胎的数量
    
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:等待连接,没有成功连接之前,会进入阻塞状态
sockfd:Socket对象描述符
addr:用于存储连接者的通信地址
addrlen:
    既是输入(告诉accetp接口,addr结构体的字节数),也是输出(实际接收到的addr结构的字节数)
返回值:建立连接的,能够通信的Socket对象描述符,失败返回-1
    
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:让sockfd对象向addr地址发起连接
sockfd:Socket对象描述符
addr:连接目标的地址
addrlen:addr结构体的字节数
返回值:成功返回0,失败返回-1
    
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:从Socket对象读接收干字节
sockfd:Socket对象描述符
buf:接收数据的内存块首地址
len:buf的字节数
flags:是否阻塞,写0即可
返回值:成功接收到了多少个字节,失败返回-1
    
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:通过Socket对象发送若干字节
sockfd:Socket对象描述符
buf:要发送的内存块首地址
len:要发送的字节数
flags:是否阻塞,写0即可
返回值:成功发送了多少个字节,失败返回-1
    
int close(int fd);
功能:关闭fd描述所代表的内核对象
    
uint16_t htons(uint16_t hostshort);
功能:把本地字节序的 unsigned short 类型的数据转换网络字节序
    
in_addr_t inet_addr(const char *cp);
功能:把字符串格式 点分十进制的ip地址 转换成网络字节序的4字节ip地址

练习1:实现基于TCP的回声服务器(收到到什么消息就返回什么消息),服务端能一对多通信。

问题1:服务器如何能服务多个客户端?

问题2:服务器如何限制客户端数量(最大在线人数)?

问题3:如何让服务端的线程循环使用,而不要频繁的创建、销毁。

服务器端:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
​
// 定义信号量
sem_t sem;
​
void* run(void* arg)
{
    // 备份sockfd
    int sockfd = *(int*)arg;
​
    char buf[BUFSIZ];
    for(;;)
    {
        // 接收数据
        int ret = read(sockfd,buf,BUFSIZ);
        if(0 >= ret || !strcmp("quit",buf))
            break;
​
        printf("recv:%s byte:%d\n",buf,ret);
​
        strcat(buf,",return!");
​
        // 返回数据
        ret = write(sockfd,buf,strlen(buf)+1);
        if(0 >= ret)
            break;
    }
    
    printf("通信结束!\n");
​
    // 关闭负责通信的socket对象
    close(sockfd);
​
    // 信号量加1
    sem_post(&sem);
}
​
int main(int argc,const char* argv[])
{
    // 创建socket对象
    int server_fd = socket(AF_INET,SOCK_STREAM,0);
    if(0 > server_fd)
    {
        perror("socket");
        return -1;
    }
​
    // 准备本机地址
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(2233);
    addr.sin_addr.s_addr = inet_addr("10.0.2.15");
    socklen_t addrlen = sizeof(addr);
​
    // 绑定socket对象和地址    
    if(bind(server_fd,(struct sockaddr*)&addr,addrlen))
    {
        perror("bind");
        return -2;
    }
​
    // 开启监听
    if(listen(server_fd,6))
    {
        perror("listen");
        return -3;
    }
​
    // 初始化信号量
    sem_init(&sem,0,5);
​
    // 循环的等待连接
    for(;;)
    {
        // 信号量减1
        sem_wait(&sem);
​
        int sockfd = accept(server_fd,(struct sockaddr*)&addr,&addrlen);
        if(0 > sockfd)
        {
            perror("accept");
            break;
        }
​
        // 创建子线程为客户端服务
        pthread_t tid;
        pthread_create(&tid,NULL,run,&sockfd);
​
        // 防止客户端同时连接,导致子线程来不及备份sockfd
        usleep(100);
​
        // 分离子线程
        pthread_detach(tid);
    }
​
    // 关闭负责连接的socket对象
    close(server_fd);
​
    // 关闭主线程,不能让它影响子线程为客户端服务
    pthread_exit(NULL);
    return 0;
}

用户端: 

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
​
int main(int argc,const char* argv[])
{
    // 创建socket对象
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(0 > sockfd)
    {
        perror("socket");
        return -1;
    }
​
    // 准备服务端的地址
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(2233);
    addr.sin_addr.s_addr = inet_addr("10.0.2.15");
    socklen_t addrlen = sizeof(addr);
​
    // 连接
    if(connect(sockfd,(struct sockaddr*)&addr,addrlen))
    {
        perror("connect");
        return -2;
    }
​
    char buf[BUFSIZ];
    for(;;)
    {
        printf(">>>");
        scanf("%s",buf);
​
        // 发送数据
        int ret = write(sockfd,buf,strlen(buf)+1);
        if(0 >= ret || !strcmp("quit",buf))
            break;
​
        // 接收数据
        ret = read(sockfd,buf,BUFSIZ);
        if(0 >= ret)
            break;
​
        printf("recv:%s byte:%d\n",buf,ret);
    }   
    printf("通信结束!\n");
​
    // 关闭socket对象
    close(sockfd);
    
    return 0;
}

4、基于UDP协议的Socket编程模型
被接收者需要完成的任务(服务端):
1、创建socket内核对象,内核创建完成后会返回它的描述符
    type:SOCK_DGRAM
2、准备本机地址(ip地址+端口号)
3、绑定(把本机地址与socket对象进行绑定)
​
for(;;)
{
    4、接收请求,同时接收发送者的地址
    5、返回结果,按发送者的地址进行返回
}
6、关闭Socket对象
​
发送者需要完成的任务:
1、创建socket内核对象,内核创建完成后会返回它的描述符
    type:SOCK_DGRAM
2、准备接收者的地址(ip地址+端口号)
​
for(;;)
{
    3、发送请求,根据接收者的地址发送数据
    4、接收结果,并接收返回者的地址
}
5、关闭Socket对象

5、UDP通信需要使用Socket接口
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
功能:UDP专用的数据发送函数。
dest_addr:收件人的地址。
addrlen:地址长度。
                      
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
功能:UDP专用的数据接收函数。
src_addr:发件人的地址,也是数据返回时的地址
addrlen:地址长度,既是输入也是输出

练习:参考TCP的回声服务器,实现基于UDP协议的回声服务器。

服务器端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
​
int main(int argc,const char* argv[])
{
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(0 > sockfd)
    {
        perror("socket");
        return -1;
    }
​
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(3344);
    addr.sin_addr.s_addr = inet_addr("10.0.2.15");
    socklen_t addrlen = sizeof(addr);
​
    if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
    {
        perror("bind");
        return -2;
    }
​
    char buf[BUFSIZ];
    for(;;)
    {
        int ret = recvfrom(sockfd,buf,BUFSIZ,0,
                (struct sockaddr*)&addr,&addrlen);
        if(0 >= ret)
            break;
​
        printf("recv:%s byte:%d from:%s:%hu\n",
                buf,ret,inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
​
        strcat(buf,",return!");
        ret = sendto(sockfd,buf,strlen(buf)+1,0,
                (struct sockaddr*)&addr,addrlen);
        if(0 >= ret)
            break;
    }
​
    printf("通信结束!\n");
    close(sockfd);
    return 0;
}

 用户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
​
int main(int argc,const char* argv[])
{
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(0 > sockfd)
    {
        perror("socket");
        return -1;
    }   
​
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(3344);
    addr.sin_addr.s_addr = inet_addr("10.0.2.15");
    socklen_t addrlen = sizeof(addr);
​
    if(connect(sockfd,(struct sockaddr*)&addr,addrlen))
    {
        perror("connect");
        return -2;
    }
​
    char buf[BUFSIZ];
    for(;;)
    {
        printf(">>>");
        scanf("%s",buf);
​
        //int ret = sendto(sockfd,buf,strlen(buf)+1,0,
        //      (struct sockaddr*)&addr,addrlen);
​
        int ret = send(sockfd,buf,strlen(buf)+1,0);
        if(0 >= ret || !strcmp("quit",buf))
            break;
​
        // ret = recvfrom(sockfd,buf,BUFSIZ,0,
        //      (struct sockaddr*)&addr,&addrlen);
        ret = recv(sockfd,buf,BUFSIZ,0);
        if(0 >= ret)
            break;
​
        printf("recv:%s byte:%d from:%s:%hu\n",buf,ret,inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
    }
    
    printf("通信结束!\n");
    close(sockfd);
    return 0;
}

6、UDP的连接操作

UPD协议底层是否需要连接操作,客户端但可以在Socket层面进行连接,连接后的Socket对象在后续的通信过程中就不再需要通信地址了。

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

int main(int argc,const char* argv[])
{
	if(3 != argc)
	{
		puts("Use:./cmd <ip> <port>");
		return 0;
	}

	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	if(0 > sockfd)
	{
		perror("socket:");
		return -1;
	}

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(atoi(argv[2]));
	addr.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t addrlen = sizeof(addr);

    // UDP协议的sockfd对象连接后,在通信时就不需要提供地址了
	if(connect(sockfd,(struct sockaddr*)&addr,addrlen))
	{
		perror("connect");
		return -2;
	}

	char buf[BUFSIZ];
	for(;;)
	{
		printf(">>");
		scanf("%s",buf);

		int ret = send(sockfd,buf,strlen(buf)+1,0);
		if(0 > ret)
			break;

		ret = recv(sockfd,buf,BUFSIZ,0);
		if(0 > ret)
			break;

		printf("recv:%s byte:%d\n",buf,ret);
	}

	close(sockfd);
}

7、本地Socket通信

网络Socket通信是把网卡抽象成Socket文件配合TCP/IP协议簇,能够使当前进程与其它计算机的进程进行网络通信。

本地Socket通信是在文件系统中创建Socket文件,能够使当前进程与本机的其它进程进行通信(IPC进程间通信)。

使用sockaddr_un类型的通信地址,当调用socket对象与通信地址绑定时,会自动创建socket文件。

// 本地地址类型
#include <sys/un.h>
struct sockaddr_un 
{
    sa_family_t sun_family;		// AF_UNIX, AF_LOCAL
    char        sun_path[];		// 套接字文件路径
};

#include <sys/socket.h>
int bind (int sockfd, const struct sockaddr* addr,socklen_t addrlen);
8、基于TCP协议的本地Socket通信

服务器端:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

int main(int argc,const char* argv[])
{
	int sockfd = socket(AF_LOCAL,SOCK_STREAM,0);
	if(0 > sockfd)
	{
		perror("socket");
		return -1;
	}	

	struct sockaddr_un addr = {};
	addr.sun_family = AF_LOCAL;
	strcpy(addr.sun_path,"tcp_socket");
	socklen_t addrlen = sizeof(addr);

	if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
	{
		perror("bind");
		return -1;
	}

	if(listen(sockfd,3))
	{
		perror("listen");
		return -1;
	}

	for(;;)
	{
		int clifd = accept(sockfd,NULL,0);
		if(0 > clifd)
		{
			perror("accept");
			sleep(1);
			continue;
		}

		if(fork())
			continue;

		char buf[4096];
		size_t buf_size = sizeof(buf);

		for(;;)
		{
			printf("recv...\n");
			int ret = recv(clifd,buf,buf_size,0);
			if(0 >= ret || 0 == strcmp("quit",buf))
			{
				printf("结束通信!");
				break;
			}

			buf[ret] = '\0';
			printf("recv:%s byte:%d\n",buf,ret);
			strcat(buf,":return");

			ret = send(clifd,buf,strlen(buf)+1,0);
			if(0 >= ret)
			{
				printf("结束通信!");
				break;
			}
		}

		close(clifd);
		return 0;
	}
	return 0;
}

 用户端:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

int main(int argc,const char* argv[])
{
	int sockfd = socket(AF_LOCAL,SOCK_STREAM,0);
	if(0 > sockfd)
	{
		perror("sockfd");
		return -1;
	}

	struct sockaddr_un addr = {};
	addr.sun_family = AF_LOCAL;
	strcpy(addr.sun_path,"tcp_socket");
	socklen_t addrlen = sizeof(addr);

	if(connect(sockfd,(struct sockaddr*)&addr,addrlen))
	{
		perror("connect");
		return -1;
	}

	char buf[4096];
	size_t buf_size = sizeof(buf);

	for(;;)
	{
		printf(">>>");
		scanf("%s",buf);

		int ret = send(sockfd,buf,strlen(buf)+1,0);
		if(0 >= ret || 0 == strcmp("quit",buf))
		{
			printf("通信结束!\n");
			break;
		}

		ret = recv(sockfd,buf,buf_size,0);
		if(0 >= ret)
		{
			printf("通信结束!\n");
			break;
		}
		
		printf("recv:%s byte:%d\n",buf,ret);
	}

	close(sockfd);
	return 0;
}

任务:参考TCP的本地Socket地址,实现基于UDP的本地Socket通信

服务器端:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

int main(int argc,const char* argv[])
{
	// 创建socket对象
	int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0);
	if(0 > sockfd)
	{
		perror("socket");
		return -1;
	}

	// 准备通信地址(本机的)
	struct sockaddr_un addr = {}, src_addr = {};
	addr.sun_family = AF_LOCAL;
	strcpy(addr.sun_path,"udp_ctos");
	socklen_t addrlen = sizeof(addr);

	// 绑定地址与socket对象
	if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
	{
		perror("bind");
		return -1;
	}

	char buf[4096];
	size_t buf_size = sizeof(buf);

	for(;;)
	{
		// 接收数据
		int size = recvfrom(sockfd,buf,buf_size,0,(struct sockaddr*)&src_addr,&addrlen);

		printf("from:%s recv:%s byte:%d\n",src_addr.sun_path,buf,size);
		strcat(buf,":return");

		// 返回数据
		sendto(sockfd,buf,strlen(buf)+1,0,(struct sockaddr*)&src_addr,addrlen);
	}

	return 0;
}

用户端: 

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

int main(int argc,const char* argv[])
{
	// 创建socket对象
	int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0);
	if(0 > sockfd)
	{
		perror("socket");
		return -1;
	}

	// 准备通信地址(自己的)
	struct sockaddr_un addr = {}, dest_addr = {};
	addr.sun_family = AF_LOCAL;
	strcpy(addr.sun_path,"udp_stoc");
	socklen_t addrlen = sizeof(addr);
	
	// 绑定地址与socket对象
	if(bind(sockfd,(struct sockaddr*)&addr,addrlen))
	{
		perror("bind");
		return -1;
	}
	
	// 目标的
	dest_addr.sun_family = AF_LOCAL;
	strcpy(dest_addr.sun_path,"udp_ctos");

	char buf[4096];
	size_t buf_size = sizeof(buf);

	for(;;)
	{
		printf(">>>");
		scanf("%s",buf);
		if(0 == strcmp("quit",buf))
		{
			printf("通信结束!");
			close(sockfd);
			break;
		}

		sendto(sockfd,buf,strlen(buf)+1,0,(struct sockaddr*)&dest_addr,addrlen);
		int size = recvfrom(sockfd,buf,buf_size,0,(struct sockaddr*)&dest_addr,&addrlen);
		printf("recv:%s byte:%d\n",buf,size);
	}
}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值