Socket简介:
socket(简称 套接字) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
Socket常用API:
头文件:
#include <sys/types.h>
#include <sys/socket.h>
API:
/*创建套接字*/
int socket(int domain, int type, int protocol);
参数:
-domain: 表示指定通信域(通信地址族)一般使用如下三种:
AF_UNIX 使用本地域套接字的地址结构,用于本地通信
AF_INET 使用IPv4的通信地址结构
AF_INET6 使用IPv6的通信地址结构
-type: 指定套接字类型,一般使用如下三种:
SOCK_STREAM:流式套接字,提供可靠的、面向连接的通信流;它使用TCP,从而保证数据传输的可靠性和顺序性,使用了此套接字后协议默认为TCP协议。
SOCK_DGEAM:数据报套接字,定义了一种不可靠、面向无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。使用了此套接字数据报协议默认为UDP
SOCK_RAM:原始套接字,允许直接访问底层协议,如IP或ICMP,它功能强大但使用较为不便,主要用于协议开发。
-protocol: 指定协议:当时用流式套接字SOCK_STREAM和数据报套接字SOCK_DGEAM时可以写为0,因为他们两个有默认的协议,当时用其他的套接字时在具体做定义。
/*绑定通信结构体*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
-sockfd:socket函数生成的套接字,也就是socket函数的返回值
-addr:通信结构体,注意此处的结构体类型为通用地址族结构体,他的结构为:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
我们在编程时使用的结构体一般为对应的通信域的结构体,如选择AF_INET(IPv4通信地址结构),他的结构体定义为
struct sockaddr_in {
sa_family_t sin_family; /* 地址族: AF_INET */
in_port_t sin_port; /* 网络字节序的端口号 */
struct in_addr sin_addr; /*IP地址结构体 */
};
struct sockaddr {
uint32_t s_addr; /* 网络字节序的IP地址 */
};
他们之间是有区别的,所以在使用时应该进行强制类型转换,实例见最后的代码。
-addrlen:通信结构体的长度
/*监听套接字*/
int listen(int sockfd, int backlog);
sockfd:socket函数生成的套接字
/*处理客户端发起的连接,生成新的套接字*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);此函数的返回值为用来进行通信的新的套接字,读写操作都使用新的套接字作为参数。
参数:
-sockfd: 函数socket生成的套接字
-addr:客户端的地址族信息
-addrlen:地址族结构体的长度
/*向服务器发起连接请求*/
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数同上
通信过程:
1.通信之前,客户和服务器先创建套接字,使用socket函数来创建且服务器端和客户端都需要此操作。
2.服务器端调用 bind,把端口号和本地 IP 地址填写到已创建的套接字中。要注意区分好服务器端和客户端,服务器端才需要调用此函数,客户端只需要创建好套接字后发送连接请求即可。
3.服务器端调用 listen(收听),把套接字设置为被动方式,以便随时接受客户的服务请求。UDP 服务器不使用 listen 系统调用。
4.服务器端调用 accept(接受),以便把远地客户进程发来的连接请求提取出来。UDP 服务器不使用 accept 系统调用。
5.客户进程调用 connect,以便和远地服务器建立连接(这就是主动打开)。
6.客户和服务器在 TCP 连接上使用 write传送数据,使用 read接收数据,客户端使用的是最开始socket出来的套接字作为write和read的参数fd,而服务器端则是使用accept函数的返回值作为其参数。
7.客户或服务器通信结束,调用 close 释放连接和撤销套接字。服务器端需要close两次 ,分别是socket函数返回的套接字和accept函数返回的套接字。
代码实例:
目的:实现在本机上互相通信
服务器端接收客户端发送
服务器端:
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, const char *argv[])
{
int fd,newfd,ret;
//使用传参的方式来传入IP地址和端口号,第一个参数为Ip地址
//参数个数判断;
if(argc < 3){
printf("too few arguments\n");
return 0;
}
char buf[BUFSIZ];//BUFSIZ为一个常量,数值为8192
fd = socket(AF_INET,SOCK_STREAM,0);//第一步:创建套接字
if( fd < 0 ){
perror("socket");
return 0;
}
//初始化bind函数的第二个参数-通信结构体
//因为我们是用的IPv4的互联网通信协议,所以family的参数为AF_INET
struct sockaddr_in soc;
//htons 用来将主机字节顺序转换为网络字节顺序
soc.sin_family = AF_INET;
soc.sin_port = htons(atoi(argv[2]));
if (inet_aton(argv[1], &soc.sin_addr) == 0) {
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
//绑定通信结构体
ret = bind(fd,&soc,sizeof(soc));
if(ret < 0){
perror("bind");
return 0;
}
//监听套接字
ret = listen(fd,5);
if(ret < 0){
perror("listen");
return 0;
}
newfd = accept(fd,NULL,NULL);
if(newfd < 0){
perror("newfd");
return 0;
//循环的读
while(1){
memset(buf,0,BUFSIZ);
ret = read(newfd,buf,BUFSIZ);
if(ret == 0)
break;
printf("%s",buf);
}
close(newfd);
close(fd);
return 0;
}
客户端:
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, const char *argv[])
{
if(argc < 3){
printf("too few arguments\n");
return 0;
}
int fd;
char buf[BUFSIZ];
fd = socket(AF_INET,SOCK_STREAM,0);
if( fd < 0 ){
perror("socket");
return 0;
}
struct sockaddr_in soc;
soc.sin_family = AF_INET;
soc.sin_port = htons(atoi(argv[2]));
if (inet_aton(argv[1], &soc.sin_addr) == 0) {
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
if(connect(fd,(struct sockaddr *)&soc,sizeof(soc)) == -1){
perror("connect");
return 0;
}
while(1){
printf("Please Input->");
fgets(buf,BUFSIZ,stdin);
write(fd,buf,strlen(buf));
}
close(fd);
return 0;
}