通过socket来进行一个TCP通信

本文深入解析TCP/IP网络通信的基本原理,包括socket编程接口的详细说明,如socket、bind、listen、accept等函数的使用方法及注意事项。同时,探讨了服务器与客户端的通信流程,以及如何处理常见的网络编程问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先放一个通信的图

在这里插入图片描述

服务器端:

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

1.int socket(int domain, int type, int protocol);
功能:创建一个用于网络通信的套接字文件。
参数:domain 地址族;AF_INET 表示ipv4 的网络程序
AF_UNIX/AF_LOCAL 表示单机进程程序
type 套接字类型;SOCK_STREAM 流式套接字,tcp程序
SOCK_DGRAM 用户数据报套接字,udp程序

protocol 应用层协议类型,0 表示自动匹配;
返回值:成功 返回套接字文件的描述符
失败 -1;

	他本质是一个函数调用,混合了传输层和网络层,他也是提供了一个接口
	例子:int sfd = socket(AF_INET,SOCK_STREAM,0);

2.int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:该函数可以将已经生成的套接字文件与本地网卡绑定;
参数:sockfd 套接字文件描述符,一般是socket的返回值;
addr 本地网卡地址信息结构体,通用结构如下:
struct sockaddr
{
sa_family_t sa_family; ////地址族
char sa_data[14]; ////地址信息
};
[14]内容是是么

	由于以上结构不能直观配置网卡信息,所有有如下变种;
	struct sockaddr_in
	{
		u_short 		sin_family;  //地址族,AF_INET
		u_short     	sin_port;    //端口号,注意字节序
		struct in_addr 	sin_addr;    //ip地址,32位的数字
		unsigned char   sin_zero[6]; //填充字段,一般不用
	}
	其中: struct _in_addr 
		    {
		   		unsigned int  s_addr;
		   }

   addrlen 参数2 的长度;

返回值:成功 0
失败 -1;
//例子:

	struct sockaddr_in ip;
	ip.sin_family = AF_INET;
	ip.sin_port = htons(8888);
	ip.sin_addr.s_addr = inet_addr("192.168.1.10");
	//inet_pton(AF_INET,"0.0.0.0",&ip.sin_addr.s_addr);
		//如果地址填"0.0.0.0",则默认使用本机的ip地址

3、int listen(int sockfd, int backlog);
功能:该函数用于在指定的套接字上监听客户端链接;

//在tcp通信中,会有两个链表存在
			.1已经建立连接的客户端链表(已完成三次握手)
			.2正在建立连接的客户端链表(数量=backlog*2 +1)

参数:sockfd 套接字id
backlog 允许接入的客户端个数,一般用5,防止遭到(SYN攻击)
返回值: 成功 0
失败 -1;

4、接入链接 服务器端最重要的函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:该函数用于服务器从本地的端口上接入一个有效链接客户端;
如果没有链接,则函数阻塞等待;

参数: sockfd 套接字id;
addr 链接客户端的地址信息,如果不关心用NULL表示;
如果要获取客户端信息,必须事先定义同类型变量;

   addrlen  参数2 的地址长度的地址,如果不关心用NULL表示;
   			如果参数2 不是NULL,则该值必须有值;

返回值: 成功 通信套接字id !!!!!! === connfd
失败 -1;
发送数据:

接受数据:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 该函数可以从指定的通信套接字上取出网络数据;
如果没有数据到达则阻塞等待;
参数: sockfd 通信套接字
buf 存储数据的地址
len 存储数据的长度
flags 获取数据方式,0 表示阻塞等待;
返回值:成功 获取的数据长度
失败 -1;

5、close 关系套接字,注意:服务器退出时候要关闭两个套接字
通信套接字 ==》close(connfd);
文件套接字 ==> close(sockfd);

客户端

1、socket() 类似与服务器端;

2、bind()可选,不选;

3、connect() 链接,属于客户端专有函数

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:该函数用于客户端程序向目标服务器发起链接请求;
参数:sockfd 套接字id 等于socket的返回值,
等于通信套接字;
addr 要链接的目标服务器地址信息结构体,需要提前定义并
初始化,同时该值不能为空;
addrlen 要链接的目标地址结构体长度;
返回值:成功 0,sockfd 套接字就变为通信套接字;
失败 -1;

4、发送数据:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:该函数可以将用户数据通过通信套接字发送给对端;
参数: sockfd 通信套接字id == sockfd == socket() 的返回值;
buf 要发送的用户数据地址;
len 要发送的数据长度
flags 数据发送方式,0 表示阻塞发送;
返回值:成功 发送的数据长度
失败 -1;

接收数据类似发送数据操作;

5、关闭套接字 只关闭sockfd 一个就行;

其实他的过程就是服务器和客户端各创建一个套接字,然后服务器绑定ip地址和端口,然后通过listen来监听客户端的连接,通过accept来连接
而客户端九十九进行申请,然后连接服务器以后就能进行通信了

问:为什么不建议客户端进行bind()?

当客户端没有自己进行bind时,系统随机分配给客户端一个端口号,并且在分配的时候,操作系统会做到不与现有的端口号发生冲突。但如果自己进行bind,客户端程序就很容易出现问题,假设在一个PC机上开启多个客户端进程,如果是用户自己绑定了端口号,必然会造成端口冲突,影响通信
因为在一个服务器bind成功后客户端非正常退出会自动
保留一段时间的链接。所以立即链接会有 地址在使用使用的异常,

int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
///允许地址重用的开关打开。

代码添加在bind之前,即可。

tcp程序常见问题:
.1:connect连接不成功:服务器和客户端有先后顺序,服务器一定要先等待连接,然后客户端再申请连接
.2:bind是为了接受数据用的
.3:listen是维护两个链表
.4:accept是阻塞等待连接
.5:双向通信,只要建立连接,直接send,recv就可以
.6:Program received signal SIGPIPE, Broken pipe.

一般是对方主机已经断开链接;而当前进程没有对于该信号进行
错误捕获而发生段错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值