TCP数据发送之tcp_sendmsg()

这篇笔记记录的TCP协议对发送数据相关系统调用内核实现,虽然发送相关的系统调用接口由很多,但是到了TCP协议层,都统一由tcp_sendmsg()处理。

1. 发送队列

在看tcp_sendmsg()代码之前,有必要先看下发送队列的组织和使用方式。
在这里插入图片描述
注:要特别注意的是,sk_send_head跟踪的是那些尚未发送过的数据,不包括重传数据。

2. tcp_sendmsg()

该函数要完成的工作就是将应用程序要发送的数据组织成skb,然后尽可能的发出去。

@msg:要发送的数据;
@size:本次要发送的数据量
int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
		size_t size)
{
   
	struct sock *sk = sock->sk;
	struct iovec *iov;
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb;
	int iovlen, flags;
	int mss_now, size_goal;
	int err, copied;
	long timeo;

	lock_sock(sk);
	TCP_CHECK_TIMER(sk);

	//计算超时时间,如果设置了MSG_DONTWAIT标记,则超时时间为0
	flags = msg->msg_flags;
	timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);

	//只有ESTABLISHED和CLOSE_WAIT两个状态可以发送数据,其它状态需要等待连接完成;
	//CLOSE_WAIT是收到对端FIN但是本端还没有发送FIN时所处状态,所以也可以发送数据
	if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
		if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
			goto out_err;

	/* This should be in poll */
	clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);

	//每次发送都操作都会重新获取MSS值,保存到mss_now中
	mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));
	//获取一个skb可以容纳的数据量。如果不支持TSO,那么该值就是MSS,否则是MSS的整数倍
	size_goal = tp->xmit_size_goal;

	//应用要发送的数据被保存在msg中,以数组方式组织,msg_iovlen为数组大小,msg_iov为数组第一个元素
	iovlen = msg->msg_iovlen;
	iov = msg->msg_iov;
	//copied将记录本次能够写入TCP的字节数,如果成功,最终会返回给应用,初始化为0
	copied = 0;

	//检查之前TCP连接是否发生过异常
	err = -EPIPE;
	if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
		goto do_error;

	//外层循环用来遍历msg_iov数组
	while (--iovlen >= 0) {
   
		//msg_iov数组中每个元素包含的数据量都可以不同,每个元素自己有多少数据量记录在自己的iov_len字段中
		int seglen = iov->iov_len;
		//from指向要拷贝的数据起点
		unsigned char __user *from = iov->iov_base;

		//iov指向下一个数组元素
		iov++;
		//内层循环用于拷贝一个数组元素
		while (seglen > 
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是TCP实现紧急数据发送与接受的C代码示例: 发送端代码: ``` // 发送紧急数据 int send_urgent_data(int sockfd, const void *buf, size_t len) { int ret = 0; struct msghdr msg = {0}; struct iovec iov[2]; char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0}; int *pFd; if (len <= 0) { return -1; } // 设置MSG_OOB标志,用于发送紧急数据 ret = send(sockfd, buf, len, MSG_OOB); if (ret < 0) { perror("send"); return -1; } // 构造发送辅助数据 iov[0].iov_base = (void *)"U"; // 发送一个无意义字符 iov[0].iov_len = 1; iov[1].iov_base = (void *)buf; // 发送紧急数据 iov[1].iov_len = len; msg.msg_iov = iov; msg.msg_iovlen = 2; // 构造控制辅助数据部分 msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); struct cmsghdr *cmsg; // 设置SOL_SOCKET和SCM_OOBLEVEL选项,用于往控制数据块中添加辅助数据 cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_OOBINLINE; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); pFd = (int*)CMSG_DATA(cmsg); *pFd = 1; // 发送辅助数据 ret = sendmsg(sockfd, &msg, MSG_OOB); if (ret < 0) { perror("sendmsg"); return -1; } return 0; } ``` 接收端代码: ``` // 接收紧急数据 int recv_urgent_data(int sockfd, void *buf, size_t len) { int ret = 0; struct msghdr msg = {0}; struct iovec iov[2]; char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0}; int *pFd; if (len <= 0 || buf == NULL) { return -1; } // 接收紧急数据 ret = recv(sockfd, buf, len, MSG_OOB); if (ret < 0) { perror("recv"); return -1; } // 构造接收辅助数据 iov[0].iov_base = (void *)alloca(1); // 发送一个无意义字符 iov[0].iov_len = 1; iov[1].iov_base = buf; // 接收紧急数据 iov[1].iov_len = len; msg.msg_iov = iov; msg.msg_iovlen = 2; // 构造控制辅助数据部分 msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); // 接收辅助数据 ret = recvmsg(sockfd, &msg, MSG_OOB); if (ret < 0) { perror("recvmsg"); return -1; } // 解析辅助数据 struct cmsghdr *cmsg; cmsg = CMSG_FIRSTHDR(&msg); pFd = (int*)CMSG_DATA(cmsg); if (*pFd == 1) { printf("received urgent data!\n"); } return 0; } ``` 以上是一个简单的例子,仅用于说明TCP实现紧急数据发送与接受的原理。实际使用时还需要考虑更多的情况,如数据完整性、并发处理等。

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值