这里是本人对Linux网络编程的学习做一个记录,主要参考书籍如下:
- Linux高性能服务器编程
- Unix网络编程 卷1:套接字联网API
socket是一个实现内核空间与用户空间的通信的接口。
一、socket服务端
提供网络服务的一端
1、socket服务端操作过程
(1)图解socket服务端操作过程
光看图不过瘾?来上一锅代码
(2)代码实现socket服务端操作过程
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <unistd.h>
#include <string.h>
#define BACKLOG_LENGTH 5
#define BUF_SIZE 100
int main(void)
{
//第一步:创建socket
int sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock < 0) {
printf("socket create failed,the error is:%s\n", strerror(errno));
return -1;
}
//第二步:为socket绑定地址(命名socket)
struct sockaddr_in saddr;
memset(&saddr, '\0', sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
saddr.sin_port = htons(9669);
int bin = bind(sock, (struct sockaddr *)&saddr, sizeof(saddr));
if (bin < 0) {
printf("bind failed,the error is:%s\n", strerror(errno));
return -1;
}
//第三步:监听socket
int status = listen(sock, BACKLOG_LENGTH);
if (status < 0) {
printf("listen failed,the error is:%s\n", strerror(errno));
return -1;
}
//第四步:接受连接
struct sockaddr_in client;
socklen_t client_len = sizeof(client);
int conn = accept(sock, (struct sockaddr *)&client, &client_len);
if (conn < 0) {
printf("accept failed,the error is:%s", strerror(errno));
return -1;
}
//第五步:读写数据(接收和发送数据)
char buf[BUF_SIZE];
memset(buf, '\0', BUF_SIZE);
char msg[BUF_SIZE] = "this is a socket server";
int res;
res = recv(conn, buf, BUF_SIZE -1, 0);
printf("result:%d,data:%s\n", strerror(errno), buf);
//发送数据
int send_len = send(conn, msg, sizeof(msg), 0);
if (send_len <= 0) {
printf("send data failed,the error is:%s", strerror(errno));
}
//第六步:关闭连接(头文件unistd.h)
close(conn);
//第七步:关闭socket(头文件unistd.h)
close(sock);
return 0;
}
注:
- 创建socket的时候要注意选择合适的类型,这里选择SOCK_STREAM(流式,一般TCP选择这种类型;另外还有数据报式SOCK_DGRAM,一般UDP选择这种),SOCK_STREAM可以使用listen监听,否则会报operation not supported的错误
(3)运行代码看疗效
编译运行上面的代码(文件名为socket_server.c)
gcc socket_server.c -o socket_server
sudo ./socket_server
使用telnet模拟客户端连接socket server并发送一个”hello world”的数据
telnet 127.0.0.1 9669
hello world
查看socket server运行效果:
查看telnet 运行效果
注:如果未安装telnet,可以使用yum或者apt-get安装
(4)功能优化
socket server代码后,客户端(telnet)发送一次数据,它就关闭了,如果再使用telent发送数据,需要重新运行,是因为它接收数据后,就关闭了连接,最后关闭了socket,并返回了,所以这里改造一下:
将第四步、第五步和第六步放到一个无限循环中,那么它就可以一直运行,不会退出,可以支持新的连接(将第四、五、六步的代码修改为以下的形式)
struct sockaddr_in client;
socklen_t client_len = sizeof(client);
char buf[BUF_SIZE];
memset(buf, '\0', BUF_SIZE);
char msg[BUF_SIZE];
int res;
while(1) {
//第四步:接受连接
int conn = accept(sock, (struct sockaddr *)&client, &client_len);
if (conn < 0) {
printf("accept failed,the error is:%s", strerror(errno));
return -1;
}
//第五步:接收数据
res = recv(conn, buf, BUF_SIZE -1, 0);
printf("result:%d,data:%s\n", strerror(errno), buf);
//发送数据
int send_len = send(conn, msg, sizeof(msg), 0);
if (send_len <= 0) {
printf("send data failed,the error is:%s", strerror(errno));
}
//第六步:关闭连接(头文件unistd.h)
close(conn);
}
2、知识拾遗——socket服务端代码说明以及socket函数说明
(1)头文件
- stdio.h :标准输入输出库,主要提供输入输出的一些函数(如printf)
- sys/socket.h:系统socket操作库,提供socket操作的相关函数(如socket,listen等)
- netinet/in.h:网络中的ipv4地址相关结构的定义(sockaddr_in等结构体的定义)
- arpa/inet.h:ipv4地址转换函数库(如inet_addr,inet_aton,inet_ntoa等)
- sys/types.h:系统中数据定义别名定义(如size_t,__socklen_t等)
- sys/errno.h:错误相关的全局变量(如errno)
- unistd.h:unix系统标准函数库(如close函数)
- string.h:字符串操作相关函数库(包括字符串内存分配相关,如memset)
(2)socket操作函数
socket
- 作用:创建socket
- 头文件:
- 原型:extern int socket (int domain, int type, int protocol)
- 参数:
domain
:使用的协议族(PF_UNIX本地unix域,PF_INET ipv4协议,PF_INET6 ipv6协议)type
:使用的服务类型(SOCK_STREAM 流服务,一般是TCP协议使用的;SOCK_DGRAM 数据报服务,一般是UDP协议使用的)protocol
:使用的协议(通常前两个参数已经决定了它使用的的协议,这里通常使用0) - 返回值:成功则返回一个文件描述符,失败就返回-1(通过errno来获取具体错误)
bind
- 作用:绑定socket(将socket与IP地址结构体变量进行绑定)
- 头文件:
- 原型:extern int bind (int fd, const struct sockaddr * addr, socklen_t len)
- 参数:
fd
:socket文件描述符addr
:地址结构体指针变量len
:地址结构体变量的长度(所占内存大小) - 返回值:成功则返回0,失败返回-1 (通过errno来获取具体错误)
listen
- 作用:监听socket
- 头文件:
- 原型:extern int listen (int fd, int backlog)
参数:
fd
:socket文件描述符backlog
:内核监听队列的最大长度返回值:成功则返回0,失败返回-1 (通过errno来获取具体错误)
accept
- 作用:接受客户端连接(个人认为是建立连接)
- 头文件:
- 原型:extern int accept (int fd, struct sockaddr addr,socklen_t addr_len);
- 参数:
fd
:socket文件描述符sockaddr
:客户端地址结构体指针addr_len
:客户端地址结构体长度的指针 - 返回值:成功则返回连接文件描述符,失败返回-1 (通过errno来获取具体错误)
recv
- 作用:接收socket另一端发送的数据
- 头文件:
- 原型:extern ssize_t recv (int conn_fd, void * buf, size_t buf_len, int flags);
- 参数:
conn_fd
:服务端与客户端连接文件描述符buf
:用于存放接收的数据的缓冲区指针buf_len
:存放接收的数据的缓冲区长度flags
:数据收发额外控制的参数(MSG_CONFIRM等) - 返回值:成功则返回0,失败返回-1 (通过errno来获取具体错误)
send
- 作用:向socket另一端发送数据
- 头文件:
- 原型:extern ssize_t send (int conn_fd, const void *buf, size_t n, int flags);
参数:
conn_fd
:服务端与客户端连接文件描述符buf
:用于存放发送的数据的缓冲区指针buf_len
:存放发送的数据的缓冲区长度flags
:数据收发额外控制的参数(MSG_CONFIRM等)返回值:成功则返回0,失败返回-1 (通过errno来获取具体错误)
(3)其他结构体与函数
sockaddr_in
- 作用:ipv4的地址结构体
- 头文件:
成员变量:
sa_family_t sin_family
:使用的地址协议族in_port_t sin_port
:整型的端口号struct in_addr sin_addr
:ipv4结构体变量inet_addr
作用:将字符串型的ip地址转换为整型的ipv4地址(在前面的文章中有细述)
htons
- 作用:是将整型变量从主机字节顺序转变成网络字节顺序,就是整数在地址空间存储方式变为:高位字节存放在内存的低地址处
memset
- 作用:为一个指针分配指定的内存,并使用指定的字符填充
- 头文件:
- 原型:extern void memset (void s, int c, size_t n)
- 参数:
s
:被操作的指针变量c
:用来填充的字符n
:填充的长度
close
- 作用:关闭一个文件描述符
- 头文件:
- 原型:extern int close (int fd)
- 参数:
fd
:文件描述符
二、socket客户端
连接socket服务器的一端
1、socket客户端操作过程
(1)图解socket服务端操作过程
有图有真相
图样图simple,好吧,看点实际的(它确实比socket服务端简单一些)
(2)代码实现socket客户端操作过程
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#define BUF_SIZE 100
int main(void)
{
//第一步:创建socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
printf("create socket failed,the error is:%s\n", strerror(errno));
return -1;
}
//第二步:与服务端建立连接
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9669);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int conn = connect(sock, (struct sockaddr *) &saddr, sizeof(saddr));
//注:这里如果连接成功返回0,否则为-1,却不是一个文件描述符
if (conn < 0) {
printf("connect the socket server failed,the error is:%s\n", strerror(errno));
return -1;
}
//第三步:读写数据(接收和发送数据)
char buf[BUF_SIZE];
char msg[BUF_SIZE] = "welcome to client world!";
int flag = recv(sock, buf, BUF_SIZE, 0);
if (flag < 0) {
printf("receive data failed,the error is:%s\n", strerror(errno));
} else {
printf("server say:%s\n", buf);
}
//发送数据
int len = send(sock, msg, BUF_SIZE, 0);
if (len <= 0) {
printf("send data failed,the error is:%s\n", strerror(errno));
}
//第四步:关闭socket连接
close(sock);
return 0;
}
注意点:
- connect函数返回的是一个连接状态,而不是连接描述符,因此* 在send和recv中传入的还是socket对象