Linux/Unix系统一切都是文件
Linux系统中,存在一个虚拟文件系统VFS,把一切实体视为文件,包括普通文件、音视频、输入输出设备等。这样,操作系统可以可以提供统一的接口来操作任何实体。每个“文件”创建后,都有一个文件描述符(File Describer),文件描述符是一个正整数,操作系统通过文件描述符对有关文件进行操作。一般来说,每次调用创建文件的函数,都会返回一个FD,若果创建失败,则会返回负数,一般是-1 。这是了解Socket编程的基础。
Socket的理解
数据链路层、网络层、传输层的协议都是在操作系统的内核中完成的,应用层的编写是用户自定义完成的。用户应用的应用层协议需要下层协议提供的服务,而前三者的调用是Linux系统的一组系统调用,需要使用Linux系统提供的API。Socket是实现上述系统调用的API的总称。
socket通信步骤
- 创建
int scoket(int domain, int type, int protocol)
- 绑定socket与socket地址:
int bind(int socketfd, const struct sockaddr* my_addr, scoklen_t addrlen)
- 监听socket,用于服务器:
int listen(int sockfd, int backlog)
- 接受
listen
队列的一个连接,用于服务器:int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen)
- 发起连接,用于客户端:
int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen)
- 关闭连接:
int close(int fd)
Tcp数据读写
- 读取Tcp数据:
ssize_t recv(int sockfd, void* buf, size_t len, int flags)
,如果返回数据的长度小于期望的长度,则多次读 - 写入数据:
ssize_t send(int sockfd, const void* buf, size_t len, int flags)
代码编写
开发环境:Ubuntu 18.04 LTS
编译器: gcc 7.3
处理逻辑:服务器开启,如果有客户端链接,则接受连接并返回一个数据,之后关闭客户端的连接。
服务器模型
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
if(2 != argc) {
printf(" Usage: %s <port of server>\n", argv[0]);
return -1;
}
int port = atoi(argv[1]);
if(port < 0) {
printf("\nError: port error\n");
return -1;
}
int socketfd = 0;
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if(socketfd < 0) {
printf("\nError: can not create socket\n");
return -1;
}
struct sockaddr_in serv_addr;
char sendBuffer[1025];
bzero(&serv_addr, sizeof(serv_addr));
bzero(sendBuffer, sizeof(sendBuffer));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(socketfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nError: can not bind socket on port %d\n", port);
return -1;
}
if(listen(socketfd, 10) < 0) {
printf("\nError: can not listen on port %d with backlog = 5\n", port);
return -1;
}
time_t ticks;
int connfd;
while(1) {
connfd = accept(socketfd, (struct sockaddr*)NULL, NULL);
snprintf(sendBuffer, sizeof(sendBuffer), "%.24s\r\n", ctime(&ticks));
send(connfd, sendBuffer, sizeof(sendBuffer), 0);
close(connfd);
sleep(1);
}
return 0;
}
客户端模型
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc, char* argv[]) {
if(3 != argc) {
printf(" Usage: %s <ip of server> <port of server>\n", argv[0]);
return -1;
}
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
if(0 == inet_pton(AF_INET, argv[1], &serv_addr.sin_addr.s_addr)) {
printf("\nError: invalid ip\n");
return -1;
}
int port = atoi(argv[2]);
if(port < 0) {
printf("\nError: invalid port\n");
return -1;
}
serv_addr.sin_port = htons(port);
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if(socketfd < 0) {
printf("\nError: can not create scoket\n");
return -1;
}
char recvBuffer[1024];
bzero(recvBuffer, sizeof(recvBuffer));
if(connect(socketfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nError: can not connect to server\n");
return -1;
}
int n = 0;
while((n = recv(socketfd, recvBuffer, sizeof(recvBuffer) - 1, 0)) > 0) {
recvBuffer[n] = 0;
if(fputs(recvBuffer, stdout) == EOF) {
printf("\nError: fputs error\n");
return -1;
}
}
if(n < 0) {
printf("\nRead error\n");
return -1;
}
return 0;
}
运行
编译文件:
g++ server.cpp -o server
g++ client.cpp -o client
运行:
先启动服务器
./server 8001
启动客户端
./client 127.0.0.1 8001
之后客户端的shell显示时间戳数据
参考资料
参考博客:https://www.thegeekstuff.com/2011/12/c-socket-programming/?utm_source=feedburner
参考书籍:Linux高性能服务器编程