linux网络编程之套接字/TCP/IP

本文详细介绍了套接字编程的基本概念和技术细节,包括套接字的三种属性:域、类型和协议,以及如何通过socket、bind、connect、listen、accept等函数创建客户端和服务端程序,实现网络通信。

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

进程间通信:只能是同一台计算机上的不同进程之间通信。

网络编程:实现在网络中的各个计算机之间的通信。

套接字:简单的来讲,套接字就是两个应用程序之间的通信管道的终点,是一种通信机制,凭借这种机制,不同主机之间的进程可以实现通信。

套接字的三个属性:域(domain)、类型(type)、协议(protocol)

1.套接字的域

域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET(TCP/IP协议),它是指Internet网络,许多Linux局域网使用的都是该网络,当然,因特网自身用的也是它。

套接字域还有AF_UNIX(UNIX内部使用)、AF_ISO(国际标准组织协议)、AF_NS(xerox网络协议)。

2.套接字类型

流套接字:提供的是一个有序,可靠,双向字节流的连接。由类型SOCK_STREAM指定,通过TCP/IP连接实现。

数据包套接字:它对可以发送的数据包的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失,复制或乱序到达。由类型SOCK_DGRAM指定的数据包套接字不建立和维持一个连接。通过UDP/IP连接实现。

3.套接字协议

只要底层的传输机制允许不止一个协议来提供要求的套接字类型,其值默认为0。


创建套接字

socket调用创建一个套接字并返回一个套接字描述符,该描述符可以用来访问套接字。

int socket(int domain,int type,int protocol);

创建的描述符是一条通信线路的一个端点。成功返回一个套接字描述符,失败返回-1,并设置errno。

套接字地址结构:
结构struct sockaddr_un定义了一种通用的套接字地址,类型为:

struct sockaddr_un

{

    sa_family_t sun_family;       /*AF_UNIX*/

    char              sun_path;         /*pathname*/

};

这是一种通用的定义,一般都不用。

TCP/IP使用的是自己的结构体struct sockaddr_in,格式如下:

struct sockaddr_in

{

    short int sin_family;      //地址类型,一般为AF_INET

    unsigned short int sin_port;        //端口号

    struct in_addr sin_addr;        //IP地址

};

这里的struct in_addr的定义如下:

struct in_addr

{

    unsigned long int  s_addr;

};


结构体sockaddr和sockaddr_in的长度都是16字节。一般在编TCP/IP程序时,一般使用结构体sockaddr_in来设置地址,然后在需要的时候,通过强制类型转换成sockaddr类型。


函数bind

bind将进程和一个套接字联系起来。bind通常用在服务器进程为接入客户端连接建立一个套接口。

int bind(int sockfd,const struct sockaddr * my_addr,socklen_t addrlen);

参数:sockfd 是socket函数调用返回的套接字。

参数:my_addr是套接字结构的地址。

参数:addrlen设置my_addr能容纳的最大字节数。(一般为16字节)


对于客户端程序来讲,不需要bind,只需要建立连接,而服务端程序还需要调用listen和accept函数。


建立连接:

函数connect用来在一个指定的套接字上创建一个连接,函数原型:

int connect(int sockfd,const struct sockaddr * address,socklen_t addrlen);

客户端调用connect与服务端进行连接。

参数:sockfd是调用socket返回的套接字描述符

参数:address是一个地址结构,指向需要连接的地址

参数:addrlen设置了address能容纳的最大字节数

成功返回0,失败返回-1,并设置errno


服务端在套接字上监听:

函数原型:int listen(int sockfd,int backlog);

参数:sockfd是调用socket返回的套接字值

参数:backlog设置并发接入队列的最大长度,如果已达到最大,则之后的连接请求将被服务器拒绝。

成功返回0,失败返回-1,并设置errno


接收连接:

当客户端连接到服务端后,会排入队列,并用来接收一个连接请求,成功后会返回一个新的套接字描述符,同时原服务端的套接字描述符继续listen指定的端口号。

int accept(int sockfd,struct sockaddr * addr,socklen_t * addrlen);

参数:sockfd是调用socket返回的套接口描述符

参数:addr是指向结构sockaddr的地址,用来保存发起连接请求的主机地址和端口

参数:addrlen设置了addrlen能容纳的最大字节数

函数执行成功返回一个新的代表客户端的套接字,出错则返回-1。并设置errno


客户端与服务端建立连接后,还需要调用recv()和send()函数,就可以在客户端与服务端之间传输数据了。


接收数据:

ssize_t recv(int s,void * buf,size_t len,int flags);

参数:s是已经建立连接的套接字描述符

参数:buf是接收数据内存buffer地址指针。

参数:len知名buffer的大小

参数:flags一般添为0

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


发送数据:

ssize_t send(int s,void * buf,size_t len,int flags);


socket实例:

客户端程序: client.c

//linux网络编程
//网络编程可以实现网络中各个计算机之间通信,也可同一计算机的进程之间通信
//一个程序使用套接字需要四个步骤
//分配套接口和初始化
//连接
//发送或接收数据
//关闭套接字

//客户端

int main(void)
{
	//分配套接口和初始化socket函数,成功返回一个客户端的套接字描述符
	int st = socket(AF_INET,SOCK_STREAM,0);//初始化socket
	//定义一个套接字地址的结构
	struct sockaddr_in addr;
	memset(&addr,0,sizeof(addr));
	addr.sin_family = AF_INET;//设置结构地址类型为TCP/IP地址
	addr.sin_port = htons(8080);//指定一个端口号:8080,htons函数:将short类型从host字节类型到net类型的转化
	addr.sin_addr.s_addr = inet_addr("192.168.1.103");//将字符串类型的IP地址转化为int,赋值给addr结构成员

	//客户端只需要connect,不需要bind
	//调用connect连接到结构addr指定的IP地址和端口号
	//即:客户端调用connect与服务器端进行连接
	if(connect(st,(struct sockaddr *) &addr,sizeof(addr)) == -1)
	{
		printf("connect failed %s\n",strerror(errno));
		return 0;
	}

	//与服务器端已经建立了连接,接下来可以实现发送和接收操作了

	char buf[1024];
	memset(buf,0,sizeof(buf));
	while(1)
	{
		read(STDIN_FILENO,buf,sizeof(buf));//从键盘中读取用户输入
		if(send(st,buf,strlen(buf),0) == -1)//发送buf的数据
		{
			printf("send failed %s\n",strerror(errno));
			return 0;
		}
	}
	//发送和接收后,需要断开连接
	close(st);//关闭socket
	return EXIT_SUCCESS;
}

服务端程序:server.c
//服务器端
//对于服务器端,需要建立自己的套接口等待来自客户端的连接(需要用到listen和accept函数)

int main()
{
	int st = socket(AF_INET,SOCK_STREAM,0);
	int on = 1;
	if(setsockopt(st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) == -1)
	{
		printf("setsockopt failed %s\n",strerror(errno));
		return 0;
	}
	struct sockaddr_in addr;//定义一个套接字地址的结构
	memset(&addr,0,sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8080);//将本地字节顺序转化为网络字节顺序
	addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY代表这个server上的所有地址
	//服务器端程序需要将IP与server程序绑定
	if(bind(st,(struct sockaddr *) &addr,sizeof(addr)) == -1)
	{
		printf("bind failed %s\n",strerror(errno));
		return 0;
	}
	//服务器端开始监听listen,监听指定端口的客户端连接
	if(listen(st,20) == -1)
	{
		printf("listen failed %s\n", strerror(errno));
		return 0;
	}

	char s[1024];
	int client_st = 0;//client端socket
	struct sockaddr_in client_addr;//表示client端的IP地址
	for(int i = 0;i < 5;i++)
	{
		memset(&client_addr,0,sizeof(client_addr));
		socklen_t len = sizeof(client_addr);
		//accept会阻塞,直到有新的客户端连接起来,accept返回一个client的socket描述符
		//同时原来的套接口继续监听指定端口号
		//若没有客户端与服务器端连接,程序则会一直等待,直到有客户端连接才继续执行
		client_st = accept(st,(struct sockaddr *) &client_addr,&len);//accept返回一个client的socket描述符
		if(client_st == -1)
		{
			printf("accept failed %s\n", strerror(errno));
			return 0;
		}
		while(1)
		{
			memset(s,0,sizeof(1024));
			int rc = recv(client_st,s,sizeof(s),0);//recv是阻塞调用,若没有收到消息,则会挂起,此处接收的是来自客户端的套接字描符的数据
			if(rc > 0)//接收了来自client的消息
			{
				printf("recv is %s\n",s);
				memset(s,0,sizeof(s));
			}
			else
			{
				if(rc == 0)
				{
					printf("client socket closed\n");
				}else
				{
					printf("recv failed %s\n", strerror(errno));
				}
				break;
			}

		}
		close(client_st);//关闭client端socket
	}
	close(st);//关闭server端listen的socket
	return 0;
}

























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值