1. Socket简介
- Socket是什么?
- Socket是一种进程间通信的机制,通过它应用程序可以通过网络进行数据传输。Socket提供了一种跨平台的接口,使得同样的代码可以在不同的操作系统上运行。
- Socket类型
- 流式套接字(SOCK_STREAM): 基于TCP协议,提供可靠的双向字节流通信。
- 数据报套接字(SOCK_DGRAM): 基于UDP协议,提供不可靠的、无连接的通信。
- 原始套接字(SOCK_RAW): 允许直接访问底层协议,主要用于协议开发或网络分析工具。
2. Socket编程流程
-
创建Socket
int socket(int domain, int type, int protocol);
- domain : 协议族,常用的有
AF_INET
(IPv4) 和AF_INET6
(IPv6)。 - type : 套接字类型,如
SOCK_STREAM
和SOCK_DGRAM
。 - protocol : 通常设为
0
,表示由系统自动选择合适的协议。
- domain : 协议族,常用的有
-
绑定Socket
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
- 将Socket与指定的IP地址和端口绑定。
-
监听连接
int listen(int sockfd, int backlog);
- 服务器端Socket进入监听模式,
backlog
指定队列中允许的最大未处理连接数。
- 服务器端Socket进入监听模式,
-
接受连接
int accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
- 接受客户端的连接请求,创建一个新的Socket用于与客户端通信。
-
连接到服务器
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
- 客户端Socket使用此函数连接到服务器。
-
发送和接收数据
- 发送数据:
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);
- 发送数据:
-
关闭Socket
int close(int sockfd);
3. 常用API详解
-
socket()
- 功能: 创建一个新的Socket。
- 参数:
domain
: 地址族,常用的如AF_INET
表示IPv4地址。type
: Socket类型,SOCK_STREAM
或SOCK_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和多路复用是非常重要的。