套接字socket编程——TCP

Socket编程

传统的进程间通信借助内核提供的IPC机制进行,但只限于本机通信,若要跨机,必须使用网络通信(本质上也是借助内核提供的socket文件描述符)。

API函数

  • 大小端转换(网络字节序与主机字节序转换)
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
//h表示主机host,n表示网络net,s表示short,l表示long
  • IP地址转换函数
//将点分十进制IP地址转换为大端网络字节序
int inet_pton(int af, const char* src, void* dst);
param:
	af: AF_INET
	src: 字符串形式的点分十进制IP地址
	dst: 转换后的地址

//将大端网络字节序转换为点分十进制IP地址
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);
param:
	af: AF_INET
	src: 网络整形IP地址
	dst: 转换后的字符串形式的点分十进制IP地址
	size: dst长度
  • 结构体sockaddr
    man 7 ip
struct sockaddr{
	sa_family_t sa_family;
	char sa_data[14];
};
struct sockaddr_in{
	sa_family_t sin_family;
	in_port_t sin_port;
	struct in_addr sin_addr;
}
struct in_addr{
	uint32_t s_addr;
};
  • socket
创建socket
int socket(int domain, int type, int protocol);
param:
	domain: 协议版本
			AF_INET, ipv4
			AF_INET6, ipv6
	type: 协议类型
	      SOCK_STREAM 流式,TCP
		  SOCK_DGRAM 报式,UDP
	protocol: 0,表示使用对应类型的默认协议

当调用socket函数后,返回一个文件描述符,内核会提供与该文件描述符相对应的读和写缓冲区,同时还有两个队列,分别是请求连接队列和已连接队列

  • bind
将socket文件描述符和IP、port绑定
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
param:
	sockfd: 套接字
	addr: 本地IP地址和端口号
INADDR_ANY: 表示使用本机任意有效IP
	addrlen: addr所占用的内存大小
  • listen
将套接字由主动变为被动
int listen(int sockfd, int backlog);
param:
	backlog: 同时请求连接的最大个数(还未建立连接)
  • accept
获得一个连接,若当前没有连接则会阻塞
int accept(int sockfd, struct sockaddr* addr, socklen_t* addtlen);
param:
	addr: 出参,保存客户端的地址信息
	addrlen: 传入传出参数,addr变量占用的内存大小
return:
	返回一个新的文件描述符,连接套接字,用于和客户端通信

accept函数是一个阻塞函数。
内核会负责将请求队列中的连接拿到已连接队列中,调用accept函数不是说新建一个连接,而是从已连接队列中取出一个可用连接。

  • connect
连接服务器
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
param:
	addr: 服务端的地址信息
  • read/write和recv/send
读取和发送数据
ssize_t read(int fd, void* buf, size_t count);
ssize_t write(int fd, const void* buf, size_t count);
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
ssize_t send(int sockfd, const void* buf, size_t len, int flags);

注意:如果写缓冲区已满,write也会阻塞;若缓冲区没有数据,read也会阻塞

C/S模型(一对一)

一个客户端一个服务端的CS模型,多对一的模型(多进程、多线程、select、poll、epoll)见后续文章

客户端不需要绑定,绑定的话相当于固定了客户端的端口,不绑定表示内核随机分配一个未被占用的端口
客户端和服务器模型

查看监听状态和连接状态
netstat -anp
a: 显示所有
n: 以数字的方式显示
p: 显示进程信息

netstat

client代码

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

int main()
{
	int cfd = socket(AF_INET, SOCK_STREAM, 0);
	if(cfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);//要连接服务器的端口
	inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);//要连接的服务器IP地址
	int ret = connect(cfd, (struct sockaddr*)&serv, sizeof(serv));
	if(ret < 0)
	{
		perror("connect error");
		return -1;
	}

	int n = 0;
	char buf[1024];
	while(1)
	{
		memset(buf, 0, sizeof(buf));
		n = read(STDIN_FILENO, buf, sizeof(buf));
		
		write(cfd, buf, n);
		
		memset(buf, 0, sizeof(buf));
		n = read(cfd, buf, sizeof(buf));
		printf("n == [%d], buf == [%s]\n", n, buf);
		if(n <= 0)
		{
			printf("read error or server close\n");
			break;
		}
	}

	close(cfd);
	return 0;
}

server代码

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

int main()
{
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
	if(ret < 0)
	{
		perror("bind error");
		return -1;
	}

	listen(lfd, 128);
	struct sockaddr_in client;
	socklen_t len = sizeof(client);
	int cfd = accept(lfd, (struct sockaddr*)&client, &len);
	char ip[16];
	memset(ip, 0, sizeof(ip));
	printf("client: ip == [%s], port == [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)), ntohs(client.sin_port));
	printf("lfd == [%d], cfd == [%d]\n", lfd, cfd);
	
	int n = 0;
	char buf[1024];
	while(1)
	{
		memset(buf, 0, sizeof(buf));
		n = read(cfd, buf, sizeof(buf));
		if(n <= 0)
		{
			printf("read error or client close\n");
			break;
		}
		printf("n == [%d], buf == [%s]\n", n, buf);

		for(int i=0; i<n; i++)
		{
			buf[i] = toupper(buf[i]);
		}

		write(cfd, buf, n);
	}

	close(lfd);
	close(cfd);
	return 0;
}

在这里插入图片描述

【下一篇】:多进程版本

  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

编程小段

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值