首页 > 系统相关 >Linux C语言TCP协议实战

Linux C语言TCP协议实战

时间:2024-10-19 17:19:44浏览次数:9  
标签:return int TCP C语言 Linux sockfd perror include sin

文章目录


摘要:这篇文章重点是实操,基本概念讲解少,通过搭建服务端和客户端来进行简单的通信,多进程服务器,多线程服务器。
关键字:TCP,TCP涉及C语言部分相关函数讲解,基于TCP协议实现多进程服务器和多线程服务器。

1. TCP简介

TCP是网络传输层的协议,具有传输稳定,数据准确的特点。
这篇文章概念上不多讲,直接进行客户端和服务器实战练习。

1.TCP/IP传输协议,易懂!!!(这篇文章可以从了解网络各层中,了解TCP传输协议)

2.TCP三次握手,四次挥手,通俗易懂!!!(这篇中深入了解TCP数据传输稳定和数据准确的特点)

2. 搭建框图

服务端和客户端的搭建框图。
在这里插入图片描述

3. 相关函数介绍

3.1 socket函数

任务:搭建通信平台

 int socket(int domain,int type,int protocol);
参数
domain
	AF_INET
	AF_INET6
	AF_UNIX,AF_LOCAL
	AF_NETLINK
	AF_PACKET
type
 	SOCK_STREAM: 流式套接字,唯一对应于TCP
 	SOCK_DGRAM:数据报套接字,唯一对应着UDP
	SOCK_RAW:原始套接字
protocol
	一般填0,原始套接字编程时需填充
返回值
	成功返回文件描述符
	出错返回-1
如果是IPV6编程,要使用struct sockddr_in6结构体(man 7 IPV6),通常使用struct sockaddr_storage来编程。

3.2 bind函数

任务:绑定IP和端口

int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
参数
sockfd:通过socket()函数拿到的fd
	 addr:采用struct sockaddr的结构体地址,通用结构体
	 struct sockaddr{
	 sa_family_t sa_family;
	 char sa_data[4];
	 }
	 struct sockaddr_in{ 基于Internel通信结构体
	 as_family_t sin_family;
	 in_port_t sin_port;
	 struct in_addr sin_addr;
	 sin_zero; //填充字节,需清零
	}
	 struct in_addr{
	 uint32_t s_addr;
	 }
 addrlen:地址长度

3.3 listen函数

任务:设置队列长度,设置被动socket,能接收连接请求

int listen(int sockfd,int backlog);
参数:
sockfd: 通过socket()函数拿到的fd;
backLog:同时允许几路客户端和服务器进行正在连接的过程(正在三次握手),一般填5。
内核中服务器的套接字fd会维护2个链表
1.正在三次握手的客户端链表(数量=2*backlog+1)
2.已经建立好连接的客户端链表(已经完成三次握手分配好了的newfd)
返回值:
成功返回0
出错返回-1

1.backlog:内核队列的长度,在Linux2.2以前指未完成队列和已完成队列的长度之和,而在Linux2.2以后指已完成队列长度。
下面验证版本backlog队列。
1.1我使用参考代码运行程序
在这里插入图片描述
1.2 文章博主运行结果
在这里插入图片描述

代码参考文章:
listen()函数中backlog参数分析
我看了好几篇文章都有不同的见解,大家想看的可以看看:
listen函数backlog参数的一点探讨
TCP 的backlog详解及半连接队列和全连接队列

3.4 accept函数

任务:阻塞等待连接,返回一个新套接字
我之前认为三次握手是在accept函数完成,而是在内核中完成,accept只是负责在内核全连接队列中取出一个。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 
参数
sockfd:经过前面socket()创建并通过bind(),listen()设置过的fd
 addr:指向存放地址信息的结构体的首地址
获取客户端IP地址和端口号
addrlen:存放地址信息的结构体的大小
返回值
成功,返回返回已经建立连接的新的newfd
出错,返回-1

3.5 connect函数

客户端连接服务器

 int connect (int sockfd, struct sockaddr * serv_addr, int addrlen)
参数:
sockfd:通过socket()函数拿到的fd
 addr:struct sockaddr的结构体变量地址
addrlen:地址长度
返回值:
成功,返回0
失败,返回-1

3.6 send函数

跟write效果一样

ssize_t send(int sockfd,
 const void *buf,
 size_t len,
 int flags
 );
参数:
返回值:
sockfd:socket函数返回的fd
 buffer:发送缓冲区首地址
length:发送的字节
f
 lags:发送方式(通常为0),作用和write一样
MSG_DONTWAIT,非阻塞
MSG_OOB:用于TCP类型的带外数据(out of band)
成功:实际发送的字节数
失败:-1,并设置errno

3.7 recv函数

跟read同效

int recv( SOCKET s, char FAR *buf, int len, int flags);
 lag:一般填0,和read作用一样
特殊的标志:
3.UDP编程
MSG_DONTWAIT
 MSG_OOB:读取带外数据
MSG_PEEK:流

3.8 其他函数

还有recvfrom 和 sendto函数用的不多,这里不多做讲解。
关于IP和端口号主机字节序和网络字节序之间的转换函数在另一篇文章中讲解:
TCP/IP传输协议,易懂!!!

4. 实战

4.1 一对一模型

4.1.1 server.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <sys/wait.h>

#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.152"

int main()
{
	int sockfd;
	//1.socket
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd==-1)
	{
		perror("socket");
		return -1;
	}
	//2.bind
	//2.1 handling protocol
	struct sockaddr_in sin,cin;
	bzero(&sin, sizeof(sin));
	bzero(&cin, sizeof(cin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);
	sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);

	if(bind(sockfd,(struct sockaddr*)&sin, sizeof(sin))==-1)
	{
		perror("bind");
		return -2;
	}
	//3.listen
	if(listen(sockfd, 5)==-1)
	{
		perror("listen");
		return -3;
	}
	sleep(60);
	int newfd = 0;
	pid_t pid;
	socklen_t addrlen = sizeof(cin);
	while(1)
	{
		//4.accept
		printf("connecting......\n");
		newfd = accept(sockfd, (struct sockaddr*)&cin, &addrlen);
		printf("newfd:%d\n", newfd);
		if(newfd==-1)
		{
			perror("accept");
			return -4;
		}
		printf("child newfd:%d\n", newfd);
		printf("succeed to connect client, cin.sin_port:%d\n", ntohs(cin.sin_port));
		char readBuf[BUF_SIZE] = {0};
		while(1)
		{
			//5.handling data;
			recv(newfd, readBuf, BUF_SIZE, 0);
			printf("the server accepts the content from the client is:\n");
			printf("%s\n",readBuf);
			if(!strncasecmp(readBuf, QUIT_FLAG, strlen(QUIT_FLAG)))
			{
				printf("the exited\n");
				close(newfd);
				break;
			}
			bzero(readBuf, BUF_SIZE);
		
		}
	}
	close(sockfd);
	return 0;

}

4.1.2 client.c

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

#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.151"
int main()
{
	int sockfd;
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		perror("socket");
		return -1;
	}
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);
	if(inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr.s_addr)<=0)
	{
		perror("inet_pton");
		exit(1);
	}
	if(connect(sockfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
	{
		perror("connect");
		return -2;
	}
	char writeBuf[BUF_SIZE] = {0};
	while(1)
	{
		printf("====input===\n");
		fgets(writeBuf, BUF_SIZE-1, stdin);
		if(send(sockfd, writeBuf, BUF_SIZE, 0) == -1)
		{
			perror("send");
			return -3;
		}
		/*if(write(sockfd, writeBuf, strlen(writeBuf))==-1)
		{
			perror("write");
			return -3;
		}*/
		int a = strncasecmp(writeBuf, QUIT_FLAG, strlen(QUIT_FLAG));
		if(!a)
		{
			printf("exited\n");
			break;
		}
		bzero(writeBuf, BUF_SIZE);
	}
	close(sockfd);
	return 0;
}


4.1.3 终端结果

一个服务器只能等处理完这一个之后才能处理下一个。
在这里插入图片描述

4.2 多进程模型

4.2.1 server.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>

#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.152"

void myfunc(int signum)
{
	if(signum == SIGCHLD)
	{
		printf("recovery complete\n");
		waitpid(-1, NULL, WNOHANG);
	}
}
int main()
{
	int sockfd;
	//1.socket
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd==-1)
	{
		perror("socket");
		return -1;
	}
	signal(SIGCHLD, myfunc);
	//2.bind
	//2.1 handling protocol
	struct sockaddr_in sin,cin;
	bzero(&sin, sizeof(sin));
	bzero(&cin, sizeof(cin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);
	sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);

	if(bind(sockfd,(struct sockaddr*)&sin, sizeof(sin))==-1)
	{
		perror("bind");
		return -2;
	}
	//3.listen
	if(listen(sockfd, 5)==-1)
	{
		perror("listen");
		return -3;
	}
	sleep(60);
	int newfd = 0;
	pid_t pid;
	socklen_t addrlen = sizeof(cin);
	while(1)
	{
		//4.accept
		printf("connecting......\n");
		newfd = accept(sockfd, (struct sockaddr*)&cin, &addrlen);
		printf("newfd:%d\n", newfd);
		if(newfd==-1)
		{
			perror("accept");
			return -4;
		}
		pid = fork();
		if(pid<0)
		{
			perror("fork");
			return -5;
		}
		if(pid==0)
		{
			printf("child newfd:%d\n", newfd);
			printf("succeed to connect client, cin.sin_port:%d\n", ntohs(cin.sin_port));
			char readBuf[BUF_SIZE] = {0};
			while(1)
			{
				//5.handling data;
				recv(newfd, readBuf, BUF_SIZE, 0);
				printf("the server accepts the content from the client is:\n");
				printf("%s\n",readBuf);
				if(!strncasecmp(readBuf, QUIT_FLAG, strlen(QUIT_FLAG)))
				{
					printf("the exited\n");
					close(newfd);
					return 0;
				}
				bzero(readBuf, BUF_SIZE);
			}
		}
		else if(pid>0)
		{
			printf("child pid: %d, father pid: %d\n", pid, getpid());
		}
	}
	close(sockfd);
	return 0;
}

4.2.2 client.c

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

#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.151"
int main()
{
	//1.socket
	int sockfd;
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		perror("socket");
		return -1;
	}
	//初始化配置,要连接的IP和端口
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);
	if(inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr.s_addr)<=0)
	{
		perror("inet_pton");
		exit(1);
	}
	//2.connect
	if(connect(sockfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
	{
		perror("connect");
		return -2;
	}
	char writeBuf[BUF_SIZE] = {0};
	while(1)
	{
		printf("====input===\n");
		fgets(writeBuf, BUF_SIZE-1, stdin);
		if(send(sockfd, writeBuf, BUF_SIZE, 0) == -1)   //send函数跟write一个效果
		{
			perror("send");
			return -3;
		}
		/*if(write(sockfd, writeBuf, strlen(writeBuf))==-1)
		{
			perror("write");
			return -3;
		}*/
		int a = strncasecmp(writeBuf, QUIT_FLAG, strlen(QUIT_FLAG));
		if(!a)
		{
			printf("exited\n");
			break;
		}
		bzero(writeBuf, BUF_SIZE);
	}
	close(sockfd);
	return 0;
}


4.2.3 终端结果

父进程:主管accept连接,当有连接请求时,accept创建新套接字并且创建子进程。
子进程:父子专门处理accept新创建的连接。
缺点:资源消耗严重。
在这里插入图片描述

4.3 多线程模型

4.3.1 server.c

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>

#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.151"

void *pthread_handle(void *arg);
int main()
{
	int sockfd;
	//1.socket
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd==-1)
	{
		perror("socket");
		return -1;
	}
	//2.bind
	//2.1 handling protocol
	struct sockaddr_in sin,cin;
	bzero(&sin, sizeof(sin));
	bzero(&cin, sizeof(cin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);
	sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);

	if(bind(sockfd,(struct sockaddr*)&sin, sizeof(sin))==-1)
	{
		perror("bind");
		return -2;
	}
	//3.listen
	if(listen(sockfd, 5)==-1)
	{
		perror("listen");
		return -3;
	}
	int newfd = 0;
	pthread_t pthid;
	socklen_t addrlen = sizeof(cin);
	while(1)
	{
		//4.accept
		printf("connecting......\n");
		newfd = accept(sockfd, (struct sockaddr*)&cin, &addrlen);
		printf("newfd:%d\n", newfd);
		if(newfd==-1)
		{
			perror("accept");
			return -4;
		}
		pthread_create(&pthid, NULL, pthread_handle, (void *)&newfd); //创建进程
		pthread_detach(pthid);      //设置线程的分离属性,子线程不归主线程管,自己运行结束自己回收资源
	}
	close(sockfd);
	return 0;

}

void *pthread_handle(void *arg)  //线程处理函数
{
	char readBuf[BUF_SIZE] = {0};
	int ret = 0;
	int fd = *(int*)arg;
	while(1)
	{
		ret = read(fd, readBuf, BUF_SIZE); 	
		if(ret<0)
		{
			perror("read");
			break;
		}
		else if(!ret) continue;
		printf("the content of readBuf is %s\n", readBuf);
		if(!strncasecmp(readBuf, QUIT_FLAG, strlen(QUIT_FLAG)))
		{
			printf("the thread exited\n");
			break;
		}
	}
}

4.3.2 client.c

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

#define QUIT_FLAG "quit"
#define BUF_SIZE 256
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.119.151"
int main()
{
	int sockfd;
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		perror("socket");
		return -1;
	}
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);
	if(inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr.s_addr)<=0)
	{
		perror("inet_pton");
		exit(1);
	}
	if(connect(sockfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
	{
		perror("connect");
		return -2;
	}
	char writeBuf[BUF_SIZE] = {0};
	while(1)
	{
		printf("====input===\n");
		fgets(writeBuf, BUF_SIZE-1, stdin);
		if(send(sockfd, writeBuf, BUF_SIZE, 0) == -1)
		{
			perror("send");
			return -3;
		}
		/*if(write(sockfd, writeBuf, strlen(writeBuf))==-1)
		{
			perror("write");
			return -3;
		}*/
		int a = strncasecmp(writeBuf, QUIT_FLAG, strlen(QUIT_FLAG));
		if(!a)
		{
			printf("exited\n");
			break;
		}
		bzero(writeBuf, BUF_SIZE);
	}
	close(sockfd);
	return 0;
}


4.3.3 终端结果

主线程:负责accept监听创建新的套接字。
子线程:负责数据处理。
缺点:线程资源共享。
在这里插入图片描述

总结
文章主要是实操,这要求你具备一些基础知识,文章如有问题请指出。

标签:return,int,TCP,C语言,Linux,sockfd,perror,include,sin
From: https://blog.csdn.net/2301_80754203/article/details/142900163

相关文章

  • 汉诺塔问题和青蛙跳台阶问题(c语言)
     这俩道题都是利用到了函数递归的思想,其中汉诺塔问题较难理解,青蛙跳台阶则较简单汉诺塔问题题述:设有三根柱子分别时A,B,C,在A柱子上放着n个盘子,每个盘子大小不一样,从下往上盘子大小依次减小,要求将A柱子上的盘子移动到C柱,且不改变盘子顺序(由大往小排序)。规则:1.一次只能......
  • 【超详细】TCP协议
    TCP(TransmissionControlProtocol传输控制协议)传输层协议有连接可靠传输面向字节流为什么TCP是传输控制协议呢?我们以前所看到的write接口,都是把用户级缓冲区的数据拷贝到发送缓冲区中,然后数据就由TCP自主决定了,所以TCP是传输控制协议,控制了数据的传输那么TCP是如何控......
  • Linux文件实时自动同步方案(基于inotify) 支持自定义目录、 不限主机数量、支持增删改
    实现细节可以直接跳到第3节3.实现细节关键词:自动同步Linux自动同步 Linux实时同步master同步slave master与slave文件实时同步 目录1.引言背景介绍方案概述方案特点2.技术选型inotifyrsyncShell脚本3.实现细节3.1前置配置1.权限设置2.安装inotify......
  • Get “https://registry-1.docker.io/v2/“: proxyconnect tcp: dial tcp: lookup pro
    docker通过代理配置上网无法pullanbox使用代理配置文件解决1.创建代理配置文件运行以下命令创建配置文件:sudomkdir-p/etc/systemd/system/docker.service.dsudotouch/etc/systemd/system/docker.service.d/http-proxy.conf2.编辑配置文件使用nano文本编辑器打......
  • linux-command-substitution(命令替换)
    linux-command-substitution(命令替换)1什么是命令替换在有一下情况的时候,会发生命令替换:$(command)或者是反引号`command`这个命令会在子shell中执行,使用标准输出替换掉上面的命令文本。并且在管道关闭或者子进程终止前,shell会一直等待。2命令替换时为什么把换行变成了......
  • tcp协议进行传输
    一、单个用户进行连接1.客户端importjava.io.InputStream;importjava.io.OutputStream;importjava.net.Socket;importjava.util.Scanner;/*1:建立客户端的Socket服务,并明确要连接的服务器。2:如果连接建立成功,就表明,已经建立了数据传输的通道.就可以在该......
  • 【C语言】动态内存管理(上)
    本篇博客将讲解以下知识点:(1)为什么要有动态内存分配(2)malloc和free1、为什么要有动态内存分配我们已经掌握的内存开辟方式有:intval=40;//向内存中申请4个字节空间存储valchararr[10];//向内存申请10个字节空间 上述的开辟空间的方式有两个特点:(1)空间的开辟......
  • 【C语言】strncat、strncmp、strstr函数讲解
    本篇博客将讲解函数:strncat、strncmp、strstr函数注意:使用strncat、strncmp、strstr函数时要包含头文件:string.h1、strncat函数的使用(是从目标空间中第一个的‘\0’位置开始追加的)strncat函数原型: char*   strncat(char*destination,  const char* sourc......
  • Linux系统重建Grub引导的方法
    一、问题出现的原因在安装双系统时,我们都是先安装Windows系统,再安装Linux系统,这样在启动计算机时,两个系统都可以被引导启动,并在开机界面可以进行选择。这是因为Linux使用的操作系统引导加载器Grub可以引导如Windows、Linux等多种操作系统,但是Windows的操作系统引导加载器不能......
  • c语言语法(76-79)10.19
    一.定义数组1.数组定义:2.数组的特点:补:数组内部的特点:左值是读,右值是写3.数组的下标:从0开始计数4.有效的下标范围:从0开始到数组的大小-1的范围当出现以下标志表示数组的下标越界:eg.此代码中的10超过了有效下标9,所以无效会报错二.数组的例子1.eg题目:代码:三.......