【Linux】网络篇二--TCP编程



一、TCP编程实现

网络编程相关API

网络编程常用函数
	socket() 创建套接字
	bind() 绑定本机地址和端口
	connect() 建立连接
	listen() 设置监听端口
	accept() 接受TCP连接
	recv(), read(), recvfrom() 数据接收
	send(), write(), sendto() 数据发送
	close(), shutdown() 关闭套接字

1、编程步骤

在这里插入图片描述


2、socket函数

int socket (int domain, int type, int protocol);
  • domain地址族
    • AF_INET // internet 协议
    • AF_UNIX // unix internal协议
    • AF_NS // Xerox NS协议
    • AF_IMPLINK // Interface Message协议
  • type // 套接字类型
    • SOCK_STREAM // 流式套接字- TCP
    • SOCK_DGRAM // 数据报套接字- UDP
    • SOCK_RAW // 原始套接字- 直接传输
  • protocol 参数通常置为0

3、bind函数

int bind (int sockfd, struct sockaddr* addr, int addrLen);
  • sockfdsocket() 调用返回
  • addr 是指向 sockaddr 结构指针,包含本机IP 地址端口号

通用地址结构

struct sockaddr
  {    
       u_short  sa_family;  // 地址族, AF_xxx
       char  sa_data[14];   // 14字节协议地址
  };

我们使用的是:Internet协议地址结构

struct sockaddr_in
  {           
       u_short sin_family;    // 地址族, AF_INET,2 bytes
       u_short sin_port;      // 端口,2 bytes
       struct in_addr sin_addr; // IPV4地址,4 bytes 	
       char sin_zero[8];      // 8 bytes unused,作为填充
  };
// internet address  
struct in_addr
{
     in_addr_t  s_addr;  // u32 network address 
};
  • addrLen : 结构体大小sizeof (struct sockaddr_in)

一般用法:

(1)定义一个struct sockaddr_in类型的变量并清空
		struct sockaddr_in myaddr;
		memset(&myaddr, 0, sizeof(myaddr));
(2)填充地址信息
		myaddr.sin_family = PF_INET;
		myaddr.sin_port = htons(8888); 
		myaddr.sin_addr.s_addr = inet_addr(“192.168.1.100”);
(3)将该变量强制转换为struct sockaddr类型在函数中使用
		bind(listenfd, (struct sockaddr*)(&myaddr), sizeof(myaddr));

4、地址转换函数

(1)主机转网络字序

unsigned long inet_addr(char *address);

address是以’\0’结尾的点分IPv4字符串。该函数返回32位的地址数据。
如果字符串包含的不是合法的IP地址,则函数返回-1
例如:

struct in_addr addr;
addr.s_addr = inet_addr(" 192.168.1.100 ");

(2)网络转主机字序

char* inet_ntoa(struct in_addr address);

addressIPv4地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针。如果错误则函数返回NULL


5、listen函数

int listen (int sockfd, int backlog);
  • sockfd: 监听连接的套接字
  • backlog:
    指定了正在等待连接最大队列长度,它的作用在于处理可能同时出现的几个连接请求。

返回值: 0 或 -1

完成listen()调用后,socket变成了监听socket(listening socket).


6、accept函数

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, 
socklen_t *addrlen) ;

返回值:已建立好连接的套接字或-1

  • sockfd : 监听套接字
  • addr : 对方地址
  • addrlen:地址长度

7、connect函数

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);  

返回值:0 或 -1

  • sockfd : socket返回的文件描述符
  • serv_addr : 服务器端的地址信息
  • addrlen : serv_addr的长度

注:
connect()是客户端使用的系统调用。
listen()accept()是TCP服务器端使用的函数


8、send函数

#include <sys/socket.h>
ssize_t  send(int socket, const void *buffer, size_t length, int flags);

返回值:成功实际发送的字节数,失败-1, 并设置errno

  • buffer : 发送缓冲区首地址
  • length : 发送的字节数
  • flags : 发送方式(通常为0)
    一般填写0,此时和write()作用一样
    特殊的标志:
    * MSG_DONTWAIT: Enables nonblocking operation; 非阻塞版本
    * MSG_OOB:用于发送TCP类型的带外数据(out-of-band)

9、recv函数

#include <sys/socket.h>
ssize_t  recv(int socket, const void *buffer, size_t length, int flags);

返回值:成功实际接收的字节数,失败-1, 并设置errno

  • buffer : 发送缓冲区首地址
  • length : 发送的字节数
  • flags : 接收方式(通常为0)
    • MSG_DONTWAIT: Enables nonblocking operation; 非阻塞版本
    • MSG_OOB:用于发送TCP类型的带外数据(out-of-band)
    • MSG_PEEK
      接收消息的开头,但不删除该消息数据。 因此,随后的接收将
      返回相同的数据

10、read()/write()

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
  • read()和write()经常会代替recv()和send()
  • 通常情况下,看程序员的偏好
    使用read()/write()和recv()/send()时最好统一

11、套接字的关闭

int close(int sockfd); //关闭双向通讯
int shutdown(int sockfd, int howto);
  • TCP连接双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown
  • 针对不同的howto,系统回采取不同的关闭方式。
    • howto = 0
      关闭读通道,但是可以继续往套接字写数据。
    • howto = 1
      和上面相反,关闭写通道。只能从套接字读取数据。
    • howto = 2
      关闭读写通道,和close()一样

示例

客机向服务器写数据,服务器读数据并打印

net.h-包含的头文件以及宏定义

/*************************************************************************
	> File Name: net.h
	> Author: xiuchengzhen
	> CSDN: xiuchengzhen.blog.csdn.net
	> Created Time: Tue 29 Mar 2022 05:45:13 AM PDT
 ************************************************************************/

#ifndef NET_H
#define NET_H

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

#define SER_PORT 5001   //端口号
#define SER_ADDR "192.168.125.128"  //ip地址
#define BUFSIZE 15  //发送数据的大小(字节)
#define USER_QUIT "quit" (退出命令)

#endif

服务器sever.c:

/*************************************************************************
  > File Name: sever.c
  > Author: xiuchengzhen
  > CSDN: xiuchengzhen.blog.csdn.net
  > Created Time: Tue 29 Mar 2022 01:41:36 AM PDT
 ************************************************************************/
#include "net.h"

int main(int argc, const char *argv[])
{
	/********socket文件创建***********/
	int fd = socket( AF_INET, SOCK_STREAM, 0);
	if(fd < 0)
	{
		perror("socket");
		exit(-1);
	}

	/*******bind设置*******/
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));  //清零操作

	sin.sin_family = AF_INET; //地址族
	sin.sin_port = htons(SER_PORT);
#if 0
	sin.sin_addr = inet_addr(SER_ADDR);
#else
	if(inet_pton(AF_INET, SER_ADDR, (void *)&sin.sin_addr) == 0)
	{
		perror("inet_pton:");
		exit(-1);
	}
#endif
	if(bind(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
	{
		perror("bind:");
		exit(-1);
	}

	/********listen*********/
	if(listen(fd, 5) < 0)
	{
		perror("listen:");
		exit(-1);
	}

	/*******accept阻塞接收*********/
	int newfd;
	if((newfd = accept(fd, NULL, NULL)) < 0)
	{
		perror("accept:");
		exit(-1);
	}

	/******read*******/
	char buf[BUFSIZE];
	int ret = -1;
	while(1)
	{
		bzero((void *)buf, BUFSIZE);  //清零
		do
		{
			ret = read(newfd, (void *)buf, BUFSIZE-1);
		}while(ret < 0 && EINTR == errno); //没读到就一直读
		if(ret < 0) //读错误
		{
			perror("read");
			exit(-1);
		}
		if(ret == 0) //连接断开
		{
			printf("client break link!\n");
			break;
		}

		printf("re:%s\n", buf);
		if(strncasecmp(buf, USER_QUIT, strlen(USER_QUIT)) == 0) 
		{
			printf("client choice break link!\n");
			break;
		}
	}

	/*********关闭网络***********/
	close(newfd);
	close(fd);

	return 0;
}

客机client.c:

/*************************************************************************
	> File Name: client.c
	> Author: xiuchengzhen
	> CSDN: xiuchengzhen.blog.csdn.net
	> Created Time: Tue 29 Mar 2022 05:43:55 AM PDT
 ************************************************************************/

#include "net.h"

int main(int argc, const char *argv[])
{
	/********socket文件创建***********/
	int fd = socket( AF_INET, SOCK_STREAM, 0);
	if(fd < 0)
	{
		perror("socket");
		exit(-1);
	}

	/*******connect设置*******/
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));  //清零操作

	sin.sin_family = AF_INET;
	sin.sin_port = htons(SER_PORT);
#if 0
	sin.sin_addr = inet_addr(SER_ADDR);
#else
	if(inet_pton(AF_INET, SER_ADDR, (void *)&sin.sin_addr) == 0)
	{
		perror("inet_pton:");
		exit(-1);
	}
#endif
	if(connect(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
	{
		perror("connect:");
		exit(-1);
	}
	
	printf("client connect success!\n");
	char buf[BUFSIZE] = {0};
	int ret = -1;
	while(1)
	{
		bzero(buf, 0);
		if(fgets(buf, BUFSIZE-1, stdin) == 0)
		{
			continue;
		}

		do
		{
			ret = write(fd, buf, strlen(buf));
		}while(ret < 0 && EINTR == errno);
	
		if (!strncasecmp (buf, USER_QUIT, strlen (USER_QUIT))) 
		{
			printf ("Client is exiting!\n");
			break;
		}
	}
	return 0;
}

运行结果:
在这里插入图片描述


二、并发编程

在这里插入图片描述

主要是应对当多个客机对服务器进行连接的情况

  • 当一个客机与服务器连接时,服务器创建一个线程或一个子进程为客机单独服务,主函数继续等待新的客机连接

1、多线程并发

实现过程在上面的例子上稍微改进
头文件net.h:

/*************************************************************************
	> File Name: net.h
	> Author: xiuchengzhen
	> CSDN: xiuchengzhen.blog.csdn.net
	> Created Time: Tue 29 Mar 2022 05:45:13 AM PDT
 ************************************************************************/

#ifndef NET_H
#define NET_H

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

#define SER_PORT 5001
#define SER_ADDR "192.168.125.128"
#define BUFSIZE 15 
#define USER_QUIT "quit"
#define BACK_LOG 5

#endif

服务器sever.c:

/*************************************************************************
  > File Name: sever.c
  > Author: xiuchengzhen
  > CSDN: xiuchengzhen.blog.csdn.net
  > Created Time: Tue 29 Mar 2022 01:41:36 AM PDT
 ************************************************************************/
#include "net.h"

void *client_hander(void *arg);

int main(int argc, const char *argv[])
{
	int i = 0;
	/********socket文件创建***********/
	int fd = socket( AF_INET, SOCK_STREAM, 0);
	if(fd < 0)
	{
		perror("socket");
		exit(-1);
	}

	/*******bind设置*******/
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));  //清零操作

	sin.sin_family = AF_INET;
	sin.sin_port = htons(SER_PORT);
	/***********优化1**********/
#if 1
	sin.sin_addr.s_addr = htonl(INADDR_ANY);   //当系统IP地址发生变化时,这里的地址也会随着变化
#else
	if(inet_pton(AF_INET, SER_ADDR, (void *)&sin.sin_addr) == 0)
	{
		perror("inet_pton:");
		exit(-1);
	}
#endif
	if(bind(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
	{
		perror("bind:");
		exit(-1);
	}

	/********listen********/
	if(listen(fd, BACK_LOG) < 0)  //设置为服务器模式,与最大接收数
	{
		perror("listen:");
		exit(-1);
	}

	/*********多线程优化*********/
	/*******accept阻塞接收*********/
	while(1)
	{
#if 0
		int newfd;
		if((newfd = accept(fd, NULL, NULL)) < 0)
		{
			perror("accept:");
			exit(-1);
		}
#else
		int newfd[BACK_LOG];
		struct sockaddr_in cin;
		char client_ip[16] = {0};
		socklen_t cin_len = (socklen_t)sizeof(cin);

		memset(&cin, 0, sizeof(cin));

		if((newfd[i] = accept(fd, (struct sockaddr *)&cin, &cin_len)) < 0)  //阻塞接收
		{
			perror("accept:");
			exit(-1);
		}

		if(inet_ntop(AF_INET, (void *)&cin.sin_addr, client_ip, sizeof(cin)) == NULL)
		{
			perror("inet_ntop:");
			exit(-1);
		}
		printf("client Port:%d,IP:%s\n",ntohs(cin.sin_port), client_ip);
#endif

		pthread_t tid;
		if(pthread_create(&tid, NULL, client_hander, (void *)&newfd[i]) != 0)
		{
			printf("tid create error!\n");
			exit(-1);
		}
		i++;
	}
	close(fd);

	return 0;
}

void *client_hander(void *newfd)
{
	pthread_detach(pthread_self());  //设置线程分离属性,线程结束时自动回收
	/******read*******/
	char buf[BUFSIZE];
	int ret = -1;
	while(1)
	{
		bzero((void *)buf, BUFSIZE);
		do
		{
			ret = read(*(int *)newfd, (void *)buf, BUFSIZE-1);
		}while(ret < 0 && EINTR == errno);
		if(ret < 0)
		{
			perror("read");
			exit(-1);
		}
		if(ret == 0)
		{
			printf("fd:%d client break link!\n", *(int *)newfd);
			break;
		}

		printf("fd:%d read:%s\n", *(int *)newfd, buf);
		if(strncasecmp(buf, USER_QUIT, strlen(USER_QUIT)) == 0)
		{
			printf("fd:%d client choice break link!\n", *(int *)newfd);
			break;
		}
	}

	/*********关闭网络***********/
	close(*(int *)newfd);
	return NULL;
}

客机client.c:

/*************************************************************************
	> File Name: client.c
	> Author: xiuchengzhen
	> CSDN: xiuchengzhen.blog.csdn.net
	> Created Time: Tue 29 Mar 2022 05:43:55 AM PDT
 ************************************************************************/

#include "net.h"

int main(int argc, const char *argv[])
{
	if(argc < 2)
	{
		printf("Please enter the port and address at run time!\n");  //  ./运行文件 端口号 IP
		printf("For example:\n ./ file port IP\n");
		exit(-1);
	}
	/********socket文件创建***********/
	int fd = socket( AF_INET, SOCK_STREAM, 0);
	if(fd < 0)
	{
		perror("socket");
		exit(-1);
	}

	/*******connect设置*******/
	struct sockaddr_in sin;
	int port = atoi(argv[1]);
	if(port < 5000)
	{
		printf("The port number must be smaller than 5000\n");
		exit(-1);
	}
	memset(&sin, 0, sizeof(sin));  //清零操作

	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
#if 0
	sin.sin_addr = inet_addr(argv[2]);
#else
	if(inet_pton(AF_INET, argv[2], (void *)&sin.sin_addr) == 0)
	{
		perror("inet_pton:");
		exit(-1);
	}
#endif

	if(connect(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
	{
		perror("connect:");
		exit(-1);
	}
	
	printf("client connect success!\n");
	char buf[BUFSIZE] = {0};
	int ret = -1;
	while(1)
	{
		bzero(buf, 0);
		if(fgets(buf, BUFSIZE-1, stdin) == 0)
		{
			continue;
		}

		do
		{
			ret = write(fd, buf, strlen(buf));
		}while(ret < 0 && EINTR == errno);
	
		if (!strncasecmp (buf, USER_QUIT, strlen (USER_QUIT))) 
		{
			printf ("Client is exiting!\n");
			break;
		}
	}
	return 0;
}

运行结果:
在这里插入图片描述


2、多进程并发

只需在服务器上改动,其他不变:

/*************************************************************************
  > File Name: sever_process.c
  > Author: xiuchengzhen
  > CSDN: xiuchengzhen.blog.csdn.net
  > Created Time: Tue 29 Mar 2022 01:41:36 AM PDT
 ************************************************************************/
#include "net.h"

void process_hander(int log);
void client_hander(int arg);

int main(int argc, const char *argv[])
{
	signal(SIGCHLD, process_hander);
	/********socket文件创建***********/
	int fd = socket( AF_INET, SOCK_STREAM, 0);
	if(fd < 0)
	{
		perror("socket");
		exit(-1);
	}

	/*******bind设置*******/
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));  //清零操作

	sin.sin_family = AF_INET;
	sin.sin_port = htons(SER_PORT);
	/***********优化1**********/
#if 1
	sin.sin_addr.s_addr = htonl(INADDR_ANY);   //当系统IP地址发生变化时,这里的地址也会随着变化
#else
	if(inet_pton(AF_INET, SER_ADDR, (void *)&sin.sin_addr) == 0)
	{
		perror("inet_pton:");
		exit(-1);
	}
#endif
	if(bind(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
	{
		perror("bind:");
		exit(-1);
	}

	/********listen********/
	if(listen(fd, BACK_LOG) < 0)  //设置为服务器模式,与最大接收数
	{
		perror("listen:");
		exit(-1);
	}

	/*********多进程优化*********/
	/*******accept阻塞接收*********/
	while(1)
	{
#if 0
		int newfd;
		if((newfd = accept(fd, NULL, NULL)) < 0)
		{
			perror("accept:");
			exit(-1);
		}
#else
		int newfd;
		struct sockaddr_in cin;
		char client_ip[16] = {0};
		socklen_t cin_len = (socklen_t)sizeof(cin);

		memset(&cin, 0, sizeof(cin));

		if((newfd = accept(fd, (struct sockaddr *)&cin, &cin_len)) < 0)  //阻塞接收
		{
			perror("accept:");
			exit(-1);
		}

		if(inet_ntop(AF_INET, (void *)&cin.sin_addr, client_ip, sizeof(cin)) == NULL)
		{
			perror("inet_ntop:");
			exit(-1);
		}
		printf("client Port:%d,IP:%s\n",ntohs(cin.sin_port), client_ip);
#endif
		pid_t pid = fork();
		if(pid < 0)
		{
			perror("fork:");
			exit(-1);
		}
		else if(pid == 0)
		{
			close(fd); 
			client_hander(newfd);

			return 0;
		}
		else
			close(newfd);  //父进程关闭,减少资源浪费
	}
	close(fd);

	return 0;
}

void process_hander(int log)
{
	if(log == SIGCHLD)
		wait(NULL);
}

void client_hander(int newfd)
{
	pthread_detach(pthread_self());  //设置线程分离属性,线程结束时自动回收
	/******read*******/
	char buf[BUFSIZE];
	int ret = -1;
	int pid = getpid();
	while(1)
	{
		bzero((void *)buf, BUFSIZE);
		do
		{
			ret = read(newfd, (void *)buf, BUFSIZE-1);
		}while(ret < 0 && EINTR == errno);
		if(ret < 0)
		{
			perror("read");
			exit(-1);
		}
		if(ret == 0)
		{
			printf("pid:%d client break link!\n", pid);
			break;
		}

		printf("pid:%d read:%s\n", pid, buf);
		if(strncasecmp(buf, USER_QUIT, strlen(USER_QUIT)) == 0)
		{
			printf("pid:%d client choice break link!\n", pid);
			break;
		}
	}

	/*********关闭网络***********/
	close(newfd);
}

运行结果
在这里插入图片描述


到这里就结束啦!
在这里插入图片描述

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Linux网络编程中,TCP是一种可靠的传输协议,用于在网络上建立可靠的连接。TCP连接具有以下特点和问题: 1. TCP连接不能同时打开多次,当一个TCP连接处于TIME_WAIT状态时,无法立即使用该连接占用的端口来建立新的连接。如果需要强制立即使用处于TIME_WAIT状态的连接所占用的端口,可以通过setsockopt()方法设置socket选项SO_REUSEADDR来实现。 2. TCP是面向字节流的协议,发送端执行的写操作次数和接收端执行的读操作次数之间没有数量关系。这可能导致粘包问题,即发送端多次发送的数据在接收端一次性收取完成,无法准确区分出数据的边界。为了解决粘包问题,可以采用以下方法: - 发送端每次发送数据后等待接收端的回复再进行下一次发送。 - 定义协议,通过在数据中添加特定的标识来区分数据的边界,例如使用【长】【宽】【高】表示长度、宽度和高度。 3. TIME_WAIT状态存在的意义主要有两个: - 可靠地终止TCP连接,确保双方都已经完全接收了对方的所有数据。 - 确保迟到的TCP报文有足够的时间被识别并丢弃,特别是在服务器主动关闭连接时。 总结起来,在Linux网络编程中,TCP作为一种可靠的传输协议,需要注意处理TIME_WAIT状态和粘包问题。可以通过设置socket选项和制定协议来解决这些问题,以保证网络连接的可靠性和数据的准确传输。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Linux网络编程 | TCP详解](https://blog.csdn.net/weixin_52983138/article/details/125077602)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

修成真

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值