一、socket套接字
在网络编程中,socket是一个网络编程接口,是一种特殊的文件描述符,用来实现不同主机上的应用程序之间的通信。
二、套接字分类
1.流套接字(SOCK_STREAM)
流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了TCP协议。
2.数据报套接字(SOCK_DGRAM)
数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
3.原始套接字(SOCK_RAW)
原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接。
三、API接口函数详解
1.socket函数
功能:创建一个新的socket,向系统申请一个socket资源。
原型:int socket(int domain, int type, int protocol);
参数:
domain:指定协议域,决定socket的地址类型,如一般使用的AF_INET决定了使用ipv4地址(32位)与端口号(16位)的组合。
type:socket类型,SOCK_STREAM(流套接字)、SOCK_DGRAM(数据报套接字)。
protocol:指定协议,一般填0。
返回值:成功返回socket套接字,否则返回-1,errno被设置。
2.bind函数
功能:绑定通信的地址和端口到socket上。
原型:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
sockfd:创建好的socket套接字。
addr:保存网络地址结构体的指针。
addrlen:指定第二个实参指向的结构体的长度。
返回值:成功返回0,否则返回-1,同时errno被设置。
网络地址结构体:
第一种:struct sockaddr {
sa_family_t sin_family; //协议族
char sa_data[14]; //包含了套接字中的目标地址和端口信息
};
第二种:struct sockaddr_in {
sa_family_t sin_family; //协议族
in_port_t sin_port; //端口号
struct in_addr sin_addr; //ip地址
unsigned char sin_zero[8];//填充作用,无实际意义
};
因为第二种的端口号和IP地址是分开的,所以使用第二种方式保存IP地址和端口号,然后强转为第一种方式。
3.listen函数
功能:把主动连接的socket变为被动连接的socket,使socket可以接受其它socket的连接请求。(系统默认socket为主动连接)
原型:int listen(int sockfd, int backlog);
参数:
socket:绑定好的socket套接字。
backlog:同时能够处理连接请求的数目。
返回值:成功返回0,否则返回-1,同时errno被设置。
成功后,套接字会变成“监听套接字”;
内核会为listen状态下的socket维护两个队列:
1. 不完全连接请求队列(SYN_RECV状态);
2. 等待accept建立socket的队列(ESTABLISHED状态)。
4.accept函数
功能:如果listen监听到客户端链接上来,则将客户端加入到监听的队列里,accept会从监听的队列里获取一个请求,如果监听到客户端连接的队列为空,accpet阻塞。
原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:监听状态下的套接字。
addr:保存网络地址结构体的指针,用于保存客户端的信息。
addrlen:指定第二个实参指向的结构体的长度。
返回值:成功返回与该客户端的连接套接字的描述符,后续与该客户端的数据的交换都是通过这个连接套接字才能进行;失败返回-1,同时errno被设置。
5.connet函数
功能:向服务端发送请求。
原型:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
sockfd:连接套接字的描述符。
addr:保存要连接的服务端地址。
addrlen:第二个参数指向的结构体的长度。
返回值:成功返回0,否则返回-1,同时errno被设置。
6.send函数和sendto函数
功能:发送数据
原型:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
sockfd:服务端的是accept返回的套接字,客户端就是创建的套接字。
buf:保存需要发送的数据内存地址。
len:需要发送的数据的长度。
flags:一般填0。
addr:网络地址结构体的指针。
addrlen:网络地址结构体的长度的指针。
返回值:成功返回实际发送出去的字节数,失败返回-1,同时errno被设置。
7.recv函数和recvfrom函数
功能:接收数据。
原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
参数:
sockfd:服务端的是accept返回的套接字,客户端就是创建的套接字。
buf:保存收到数据的内存地址。
len:需要接收的数据的长度。
flags:一般填0。
addr:网络地址结构体的指针。
addrlen:网络地址结构体的长度的指针。
四、服务端和客户端实现流程图
1.基本TCP客户/服务器程序的套接字函数 :
2.基本UDP客户/服务器程序的套接字函数:
五、数据在网络上传输的问题
1.网络字节序的问题
网络上传输的数据都是字节流,在多字节数值发送之前,数据应该要以大端模式存放,因为TCP/UDP/IP协议规定:把接收到的第一个字节当做高位字节看待,要求发送方发送的第一个字节必须得是高位。
整数在主机字节序与网络字节序之间的转换函数:
uint32_t htonl(uint32_t hostlong); //把一个32bits的整数的主机字节序转换成网络字节序
uint16_t htons(uint16_t hostshort); //把一个16bits的整数的主机字节序转换成网络字节序
uint32_t ntohl(uint32_t netlong); //把一个32bits的整数的网络字节序转换成主机字节序 uint16_t ntohs(uint16_t netshort); //把一个16bits的整数的网络字节序转换成主机字节序
2.IP地址转换问题
IP地址是以点分十进制的形式存在的,在网络传输中,要把点分十进制的形式转换为IP的网络地址。
IP地址转换函数:
int inet_aton(const char *cp, struct in_addr *inp);
cp:指向要转换的点分十进制字符串
inp:指向一个IP的地址结构体struct in_addr用于保存转换后的32bit的IP地址的.
返回值:成功返回0失败返回-1,同时errno被设置
in_addr_t inet_addr(const char *cp); //把cp指向的点分十进制字符串转换成IP的网络地址in_addr_t(uint32_t)。
in_addr_t inet_network(const char *cp); // 与inet_addr类似
char *inet_ntoa(struct in_addr in); // 把32bit存在形式的网络地址变成点分十进制的字符串。
六、基于TCP的服务端和客户端代码
1.TCP服务端代码如下:
#include "head.h"
//创建一个服务端套接字
//参数:IP地址 和 端口号
//返回值:成功返回0,否则返回-1
int create_socket(char * ip,int port)
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("Create socket error:");
return -1;
}
//设置套接字
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on));
//初始化ip/port
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr=inet_addr(ip);
//绑定IP
int ret = bind(sockfd, (struct sockaddr *)&server,sizeof(server));
if(-1 == ret)
{
perror("Bind error:");
close(sockfd);
return -1;
}
//进入监听模式
ret = listen(sockfd, 10);
if(-1 == ret)
{
perror("Join listen error:");
close(sockfd);
return -1;
}
return sockfd;
}
//与客户端进行通讯
void Handle_Connect(int confd,struct sockaddr_in client)
{
int ret;
while(1)
{
char buf[256] = {0};
ret = recv(confd,buf,sizeof(buf),0);
if(ret > 0)
{
printf("Recv Data [%s] From [IP:%s,PORT:%d]n",buf,inet_ntoa(client.sin_addr),ntohs(client.sin_port));
char resp[256] = {"I recv your data!"};
//回消息给客户端
send(confd,resp,strlen(resp),0);
}
}
}
int main(int argc,char **argv)
{
if(argc != 3)
{
fprintf(stderr,"USAGE: %s n",argv[0]);
return -1;
}
//1.创建已进入监听模式的服务端套接字
int sockfd = create_socket(argv[1],atoi(argv[2]));
//2.建立连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
//建立连接,返回连接套接字
int confd = accept(sockfd, (struct sockaddr *)&client, &len);
if(-1 == confd)
{
perror("Connect error:");
}
while(1)
{
pid_t pid = fork();
if(-1 == pid)
{
perror("Fork error:");
continue;
}
else if(0 == pid)
{
//与客户端的数据传递
Handle_Connect(confd,client);
}
else
{
close(confd);
}
}
close(sockfd);
return 0;
}
2.TCP客户端代码如下:
#include "head.h"
//创建一个客户端套接字
//参数:IP地址 和 端口号
//返回值:成功返回0,否则返回-1
int create_socket(char * ip,int port)
{
//1.创建套接字
int confd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == confd)
{
perror("Create socket error:");
return -1;
}
return confd;
}
int main(int argc,char **argv)
{
if(argc != 3)
{
fprintf(stderr,"USAGE: %s n",argv[0]);
return -1;
}
//1.创建套接字
int confd = create_socket(argv[1],atoi(argv[2]));
//2.建立连接
struct sockaddr_in server;
server.sin_family = AF_INET; //指定协议族
server.sin_port = htons(atoi(argv[2])); //指定网络程序的端口号
server.sin_addr.s_addr = inet_addr(argv[1]); //指定IP地址
int ret = connect(confd,(struct sockaddr *)&server,sizeof(server));
if(ret == -1)
{
perror("Connect Server Failed");
return -1;
}
char buf[256];
while(1)
{
puts("请输入要传输的数据:");
//通过套接字往服务器上发送数据
scanf("%s",buf);
send(confd,buf,strlen(buf),0);
//接收服务器回给我们的数据
recv(confd,buf,sizeof(buf),0);
printf("服务器回应:%sn",buf);
}
close(confd);
return 0;
}
七、基于UDP服务端和客户端代码
1.UDP服务端代码如下:
#include "head.h"
//创建绑定好的套接字
//成功返回套接字,否则返回-1
int create_socket(char * ip, int port)
{
//创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd)
{
perror("Socket creatr error:");
return -1;
}
//网络地址结构体
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr=inet_addr(ip);
//绑定
int ret = bind(sockfd, (struct sockaddr *)&server,sizeof(server));
if(-1 == ret)
{
perror("Bind error:");
close(sockfd);
return -1;
}
return sockfd;
}
int main(int argc,char **argv)
{
if(argc != 3)
{
fprintf(stderr,"%s n",argv[0]);
return -1;
}
//1.创建绑定好的套接字
int sockfd = create_socket(argv[1],atoi(argv[2]));
struct sockaddr_in client;
int len = sizeof(client);
//2.接收和发送数据
while(1)
{
char buf[256] = {0};
int ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client, &len);
if(ret > 0)
{
printf("Data<%s> from IP<%s> PORT<%d> clientn",buf,inet_ntoa(client.sin_addr),ntohs(client.sin_port));
}
if(ret < 0)
{
perror("Recv false:");
continue;
}
}
close(sockfd);
return 0;
}
2.UDP客户端代码如下:
#include "head.h"
//创建绑定好的套接字
//成功返回套接字,否则返回-1
int create_socket(char * ip, int port)
{
//创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd)
{
perror("Socket creatr error:");
return -1;
}
return sockfd;
}
int main(int argc,char **argv)
{
if(argc != 3)
{
fprintf(stderr,"%s n",argv[0]);
return -1;
}
//1.创建套接字
int sockfd = create_socket(argv[1],atoi(argv[2]));
struct sockaddr_in cliten;
int len = sizeof(cliten);
cliten.sin_family = AF_INET; //指定协议族
cliten.sin_port = htons(atoi(argv[2])); //指定网络程序的端口号
cliten.sin_addr.s_addr = inet_addr(argv[1]); //指定IP地址
//2.接收和发送数据
while(1)
{
char buf[256] = {0};
printf("请输入要发送的数据:n");
scanf("%s",buf);
int ret = sendto(sockfd, buf, sizeof(buf) + 1, 0, (struct sockaddr *)&cliten, len);
if(-1 == ret)
{
perror("Sendto error:");
continue;
}
}
close(sockfd);
return 0;
}



