Linux socket通讯
socket架构
Socket是应用层与TCP/IP协议族通信的中间软件抽象层。
socket方法介绍
int socket(int protofamily, int type, int protocol);
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()
用于创建一个socket
描述符(socket descriptor)
,它唯一标识一个socket
。这个socket
描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
protofamily:即协议域,又称为协议族(family
)。常用的协议族有,AF_INET
、AF_INET6
、AF_LOCAL
(或称AF_UNIX
,Unix
域)、AF_ROUTE
等等。协议族决定了socket的
地址类型,在通信中必须采用对应的地址,如AF_INET
决定了要用ipv4
地址(32位的)与端口号(16位的)的组合、AF_UNIX
决定了要用一个绝对路径名作为地址。
type:指定socket
类型。常用的socket
类型有,SOCK_STREAM
(TCP)、SOCK_DGRAM
(UDP)、SOCK_RAW
、SOCK_PACKET
、SOCK_SEQPACKET
等等。
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP
、IPPTOTO_UDP
、IPPROTO_SCTP
、IPPROTO_TIPC
等,它们分别对应TCP
传输协议、UDP
传输协议、STCP
传输协议、TIPC
传输协议,只需要了解TCP
和UDP
既可以。
int bind(int sockfd, const struct sockaddr addr, socklen_t addrlen);
sockfd:即socket
描述字,它是通过socket()
函数创建了,唯一标识一个socket。bind()
函数就是将给这个描述字绑定一个名字。
addr:一个const struct sockaddr*指针,指向要绑定给sockfd
的协议地址。这个地址结构根据地址创建socket
时的地址协议族的不同而不同。
addrlen:对应的是地址的长度。
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];//14个字节表示端口和ip地址都在这里面
}
//网络通讯AF_INET
struct socketaddr_in{
sa_family_t sin_family; //地址族(Address Family)
uint16_t sin_port; //16位TCP/UDP 端口号
struct in_addr sin_addr; //32位IP 地址
cahr sin_zero[8];//不使用
}
//AF_UNIX
struct sockaddr_un{
uint8_t sun_len;
sa_family_t sun_family; /* AF_LOCAL */
char sun_path[104]; /* null-terminated pathname */
};
struct in_addr{
In_addr_t s_addr; //32位IPv4 地址
}
int listen(int sockfd, int backlog);
listen函数的第一个参数即为要监听的socket
描述字,第二个参数为相应socket
可以排队的最大连接个数。
int connect(int sockfd, const struct sockaddr*addr, socklen_t addrlen);
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。
int accept(int sockfd, struct sockaddr addr, socklen_t addrlen);
accept函数的第一个参数为服务器的socket
描述字,第二个参数为指向struct sockaddr *
的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。
int close(int fd);
关闭函数
ssize_t read(int fd, void buf, size_t count);
read
函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。
ssize_t write(int fd, const void buf, size_t count);
write
函数将buf中的字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理
Socket的通信过程方法调用
服务端: socket -> bind -> listen -> accept -> read/write -> close
客户端: socket -> connect -> read/write -> close
socket 网络通信的实战代码
server:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
// socket() 打开一个网络通讯端口,如果成功的话,
// 就像 open() 一样返回一个文件描述符,
// 应用程序可以像读写文件一样用 read/write 在网络上收发数据。
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
//INADDR_ANY 表示监听0.0.0.0地址,socket只绑定端口,不绑定本主机的某个特定ip,让路由表决定传到哪个ip(0.0.0.0地址表示所有地址、不确定地址、任意地址)(一台主机中如果有多个网卡就有多个ip地址)(路由表应该能知道这个端口正在由哪个ip监听)
//htons()把short型值转成按网络字节顺序排列的short型值
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//htonl()把long型值转成按网络字节顺序排列的long型值
servaddr.sin_port = htons(SERV_PORT);
// bind() 的作用是将参数 listenfd 和 servaddr 绑定在一起,
// 使 listenfd 这个用于网络通讯的文件描述符监听 servaddr 所描述的地址和端口号。
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// listen() 声明 listenfd 处于监听状态,
// 并且最多允许有 20 个客户端处于连接待状态,如果接收到更多的连接请求就忽略。
listen(listenfd, 20);
printf("Accepting connections ...\n");
while (1)
{
cliaddr_len = sizeof(cliaddr);
// 典型的服务器程序可以同时服务于多个客户端,
// 当有客户端发起连接时,服务器调用的 accept() 返回并接受这个连接,
// 如果有大量的客户端发起连接而服务器来不及处理,尚未 accept 的客户端就处于连接等待状态。
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
printf("received from %s at PORT %d\n",
//Ip地址
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
//端口号
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
{
buf[i] = toupper(buf[i]);
}
write(connfd, buf, n);
close(connfd);
}
}
client:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;
if (argc != 2)
{
fputs("usage: ./client message\n", stderr);
exit(1);
}
str = argv[1];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);//htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)
// 由于客户端不需要固定的端口号,因此不必调用 bind(),客户端的端口号由内核自动分配。
// 注意,客户端不是不允许调用 bind(),只是没有必要调用 bind() 固定一个端口号,
// 服务器也不是必须调用 bind(),但如果服务器不调用 bind(),内核会自动给服务器分配监听端口,
// 每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
write(sockfd, str, strlen(str));
n = read(sockfd, buf, MAXLINE);
printf("Response from server:\n");
write(STDOUT_FILENO, buf, n);
printf("\n");
close(sockfd);
return 0;
}
Unix Socket通信
server:
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#define MAXLINE 80
char *socket_path = "server-socket";
int main()
{
struct sockaddr_un serun, cliun;
socklen_t cliun_len;
int listenfd, connfd, size;
char buf[MAXLINE];
int i, n;
if ((listenfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket error");
exit(1);
}
memset(&serun, 0, sizeof(serun));
serun.sun_family = AF_UNIX;
strncpy(serun.sun_path,socket_path ,
sizeof(serun.sun_path) - 1);
unlink(socket_path);//这个相当于把之前的地址要移除,不然上一个server没有结束,移除会报错already in use
if (bind(listenfd, (struct sockaddr *)&serun, sizeof(struct sockaddr_un)) < 0) {
perror("bind error");
exit(1);
}
printf("UNIX domain socket bound\n");
if (listen(listenfd, 20) < 0) {
perror("listen error");
exit(1);
}
printf("Accepting connections ...\n");
while(1) {
cliun_len = sizeof(cliun);
if ((connfd = accept(listenfd, (struct sockaddr *)&cliun, &cliun_len)) < 0){
perror("accept error");
continue;
}
printf("new client connect to server,client sockaddr === %s \n",((struct sockaddr *)&cliun)->sa_data);
while(1) {
memset(buf,0,sizeof(buf));
n = read(connfd, buf, sizeof(buf));
if (n < 0) {
perror("read error");
break;
} else if(n == 0) {
printf("EOF\n");
break;
}
printf("received: %s\n", buf);
for(i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
}
write(connfd, buf, n);
}
close(connfd);
}
close(listenfd);
return 0;
}
client:
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#define MAXLINE 80
char *client_path = "client-socket";
char *server_path = "server-socket";
int main() {
struct sockaddr_un cliun, serun;
int len;
char buf[100];
int sockfd, n;
if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){
perror("client socket error");
exit(1);
}
memset(&serun, 0, sizeof(serun));
serun.sun_family = AF_UNIX;
strncpy(serun.sun_path,server_path ,
sizeof(serun.sun_path) - 1);
if (connect(sockfd, (struct sockaddr *)&serun, sizeof(struct sockaddr_un)) < 0){
perror("connect error");
exit(1);
}
printf("please input send char:");
while(fgets(buf, MAXLINE, stdin) != NULL) {
write(sockfd, buf, strlen(buf));
n = read(sockfd, buf, MAXLINE);
if ( n < 0 ) {
printf("the other side has been closed.\n");
}else {
printf("received from server: %s \n",buf);
}
printf("please input send char:");
}
close(sockfd);
return 0;
}