Linux下Socket编程

1. Socket简介

  • Socket是什么?
    • Socket是一种进程间通信的机制,通过它应用程序可以通过网络进行数据传输。Socket提供了一种跨平台的接口,使得同样的代码可以在不同的操作系统上运行。
  • Socket类型
    • 流式套接字(SOCK_STREAM): 基于TCP协议,提供可靠的双向字节流通信。
    • 数据报套接字(SOCK_DGRAM): 基于UDP协议,提供不可靠的、无连接的通信。
    • 原始套接字(SOCK_RAW): 允许直接访问底层协议,主要用于协议开发或网络分析工具。

2. Socket编程流程

  1. 创建Socket

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

    • domain : 协议族,常用的有 AF_INET(IPv4) 和 AF_INET6(IPv6)。
    • type : 套接字类型,如 SOCK_STREAMSOCK_DGRAM
    • protocol : 通常设为 0,表示由系统自动选择合适的协议。
  2. 绑定Socket

    int bind(int sockfd, const struct sockaddr *addr,

    socklen_t addrlen);

    • 将Socket与指定的IP地址和端口绑定。
  3. 监听连接

    int listen(int sockfd, int backlog);

    • 服务器端Socket进入监听模式,backlog指定队列中允许的最大未处理连接数。
  4. 接受连接

    int accept(int sockfd, struct sockaddr *addr,

    socklen_t *addrlen);

    • 接受客户端的连接请求,创建一个新的Socket用于与客户端通信。
  5. 连接到服务器

    int connect(int sockfd, const struct sockaddr *addr,

    socklen_t addrlen);

    • 客户端Socket使用此函数连接到服务器。
  6. 发送和接收数据

    • 发送数据: ssize_t send(int sockfd, const void *buf, size_t len,

      int flags);

    • 接收数据: ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  7. 关闭Socket

    int close(int sockfd);

3. 常用API详解

  • socket()

    • 功能: 创建一个新的Socket。
    • 参数:
      • domain: 地址族,常用的如 AF_INET 表示IPv4地址。
      • type: Socket类型,SOCK_STREAMSOCK_DGRAM
      • protocol: 协议编号,通常为0,由系统选择默认协议。
    • 返回值: 返回新的文件描述符,失败返回 -1
  • bind()

    • 功能: 将Socket绑定到特定的IP地址和端口。
    • 参数:
      • sockfd: 由 socket() 创建的文件描述符。
      • addr: 结构体指针,包含要绑定的地址信息。
      • addrlen: addr 的长度。
  • listen()

    • 功能: 在Socket上监听连接请求。
    • 参数:
      • sockfd: 由 socket() 创建的文件描述符。
      • backlog: 未处理连接的最大数量。
  • accept()

    • 功能: 接受连接请求,创建用于通信的新Socket。
    • 参数:
      • sockfd: 监听套接字。
      • addr: 客户端的地址信息结构体。
      • addrlen: addr 的长度。
  • connect()

    • 功能: 客户端使用此函数连接到服务器。
    • 参数:
      • sockfd: 由 socket() 创建的文件描述符。
      • addr: 服务器的地址信息。
      • addrlen: addr 的长度。
    • 返回值: 成功返回 0,失败返回 -1
  • send() 和 recv()

    • send() 用于向对方发送数据,recv() 用于接收数据。
    • 参数:
      • sockfd: 通信的套接字。
      • buf: 数据缓冲区。
      • len: 缓冲区的大小。
      • flags: 标志位,一般为0。

4. 错误处理

在Socket编程中,经常会遇到错误。通常的做法是检查函数的返回值,若为 -1 则出错,并通过 errno 查看具体的错误原因。以下是一些常见的错误:

  • EADDRINUSE: 地址已被使用。
  • EADDRNOTAVAIL: 无效的地址。
  • ECONNREFUSED: 连接被拒绝。
  • ETIMEDOUT: 连接超时。

5. 高级特性

  • 非阻塞I/O : 使用 fcntl() 设置Socket为非阻塞模式。
  • 多路复用 : 使用 select()poll() 等函数同时监听多个Socket。

6. 示例代码

服务器端
cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int clientFd;
int serverFd;
void hand(int s){
	if(2 == s){
		//7 断开连接
		close(clientFd);
		close(serverFd);
		printf("bye bye 了勾八!\n");
		exit(1);
	}
}

int main(){
	signal(2,hand);
	//1 创建socket
	serverFd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == serverFd) printf("创建socket失败:%m!\n"),exit(-1);
	printf("创建socket成功!\n");
	//2 协议地址簇
	struct sockaddr_in addr={0};
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");//ip地址 注意字符串转网络字节序
	addr.sin_port = htons(8888);//端口号 用1W左右的  
	//3 绑定
	int r = bind(serverFd,(struct sockaddr*)&addr,sizeof addr); 
	if(-1 == r) printf("绑定失败:%m!\n"),close(serverFd),exit(-1);
	printf("绑定成功!\n");
	//4 监听
	r = listen(serverFd,100);//最大容量为100
	if(-1 == r) printf("监听失败:%m!\n"),close(serverFd),exit(-1);
	printf("监听成功!\n");
	//5 接受客户端连接
	struct sockaddr_in cAddr = {0};//用来接收客户端的协议地址簇
	int len = sizeof cAddr;
	clientFd = accept(serverFd,(struct sockaddr*)&cAddr,&len);
	if(-1 == clientFd) printf("服务器崩溃:%m\n"),close(serverFd),exit(-1);
	printf("接受客户端连接成功:%d %s %u\n",clientFd,inet_ntoa(cAddr.sin_addr),cAddr.sin_port);
	//6 通信
	char buff[1024];
	int n=0;
	char temp[1024];
	while(1){
		r = recv(clientFd,buff,1023,0);
		if(r > 0) {
			buff[r] = '\0';//添加字符串结束符号 '\0'
			printf("%d:%s\n",r,buff);
			sprintf(temp,"%d-%s",n++,buff);
			send(clientFd,temp,strlen(temp),0);
		}
	}
}
客户端
cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int fd;
void hand(int s){
	if(2 == s){
		//5 断开连接
		close(fd);
		printf("bye bye !\n");
		exit(1);
	}
}

int main(){
	signal(2,hand);
	//1 创建socket
	fd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == fd) printf("创建socket失败:%m!\n"),exit(-1);
	printf("创建socket成功!\n");
	//2 协议地址簇
	struct sockaddr_in addr={0};
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");//ip地址 注意字符串转网络字节序
	addr.sin_port = htons(8888);//端口号 用1W左右的  大小端转换
	//3 连接服务器
	int r = connect(fd,(struct sockaddr*)&addr,sizeof addr);
	if(-1 == r) printf("连接服务器失败:%m!\n"),exit(-1);
	printf("连接服务器成功!\n");

	//4 通信
	char buff[1024];
	char temp[1024];
	while(1){
		printf("请输入要发送给服务器的数据:");
		scanf("%s",buff);
		r = send(fd,buff,strlen(buff),0);
		printf("发送%d字节数据到服务器!\n",r);
		r = recv(fd,temp,1023,0);
		if(r>0){
			temp[r] = '\0';
			printf("服务器回复:%s\n",temp);
		}
	}
}

7. 结论

Socket编程是网络通信的基础,通过了解各种Socket API的使用,可以实现复杂的网络应用程序。在实际编程中,掌握错误处理和高级特性如非阻塞I/O和多路复用是非常重要的。

相关推荐
长安11082 小时前
前后端、网关、协议方面补充
网络
舞动CPU4 小时前
linux c/c++最高效的计时方法
linux·运维·服务器
皮锤打乌龟4 小时前
(干货)Jenkins使用kubernetes插件连接k8s的认证方式
运维·kubernetes·jenkins
钰@5 小时前
小程序开发者工具的network选项卡中有某域名的接口请求,但是在charles中抓不到该接口
运维·服务器·小程序
wanhengwangluo5 小时前
云服务器和物理服务器的区别有哪些?
运维·服务器
hzyyyyyyyu5 小时前
隧道技术-tcp封装icmp出网
网络·网络协议·tcp/ip
南猿北者5 小时前
docker Network(网络)
网络·docker·容器
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端
yaosheng_VALVE6 小时前
稀硫酸介质中 V 型球阀的材质选择与选型要点-耀圣
运维·spring cloud·自动化·intellij-idea·材质·1024程序员节
Hacker_Nightrain6 小时前
网络安全CTF比赛规则
网络·安全·web安全