socket通信之C篇
- 服务端与客户端
- 简介
- socket通信
- 服务端与客户端通信模型
- 通信实战
- server(服务端)创建
- client(客户端)创建
- 函数详解
- 创建套接字 socket
- 绑定端口bind
- 进入监听状态listen
- 获取客户端连接请求accept
- 接收网络数据read
- 发送数据write
- 关闭套接字close
- 连接指定服务端connect
服务端与客户端
简介
服务端与客户端是计算机网络通信的两个主要角色,在物理上并没有什么区别,可以同时在一个设备,也可在不同的设备上。只不过在市场上,为了性能和更好的提供服务。大部分的服务端与客户端不在同个设备中。
客户端(client)一般指通过应用程序(例如手机app,电脑软件)或浏览器网页向服务器获取资源和数据的计算机或设备。
服务端(server)一般指提供服务的计算机或设备。服务端接收并处理客户端的请求,并返回对应的结果。
服务器指服务端所部署的计算机或设备
socket通信
以下均在linux系统中实现,socket通信是进程通信的一种方式。以下操作函数的头文件位于socket.h
服务端与客户端通信模型
通信实战
server(服务端)创建
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
int main()
{
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if(socketfd < 0)
{
printf("creat socket error\n");
return -1;
}
struct sockaddr_in socket_addr;
memset(&socket_addr, 0, sizeof(socket_addr));
socket_addr.sin_family = AF_INET;
socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket_addr.sin_port = htons(4017);
if(bind(socketfd, (struct sockaddr *)&socket_addr, sizeof(socket_addr)) < 0)
{
printf("bind port error\n");
return -1;
}
if(listen(socketfd, 5) < 0)
{
printf("listen error\n");
return -1;
}
struct sockaddr_in client_addr;
int clilen = sizeof(struct sockaddr_in);
int clifd = accept(socketfd, (struct sockaddr*)&client_addr, (socklen_t*)&clilen);
char buf[256] = {0};
char sendbuf[] = "hello";
while(1)
{
memset(buf, 0, sizeof(buf));
read(clifd, buf, 255);
printf("recv message is: %s\n", buf);
write(clifd, sendbuf, 5);
sleep(1);
}
close(clifd);
close(socketfd);
return 0;
}
client(客户端)创建
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if(socketfd < 0)
{
printf("creat socket error\n");
return -1;
}
struct sockaddr_in socket_addr;
memset(&socket_addr, 0, sizeof(socket_addr));
socket_addr.sin_family = AF_INET;
socket_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
socket_addr.sin_port = htons(4017);
if(connect(socketfd, (struct sockaddr *)&socket_addr, sizeof(socket_addr)) < 0)
{
printf("bind port error\n");
return -1;
}
char buf[256] = {0};
char sendbuf[] = "client msg";
while(1)
{
memset(buf, 0, sizeof(buf));
read(socketfd, buf, 255);
printf("recv message is: %s\n", buf);
write(socketfd, sendbuf, 11);
sleep(1);
}
close(socketfd);
return 0;
}
函数详解
创建套接字 socket
- 函数原型
int socket(int domain, int type, int protocol)
- 函数解析
作用 :用于创建一个指定协议的sockfd,即一个套接字。
例如创建一个tcp,ipv4的sockfd
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
返回: 创建失败返回-1,成功返回文件描述符。
- 参数解析
以下枚举值只列出部分
参数 | 说明 | 枚举 |
---|---|---|
domain | 指定通信域,即选择通信用的协议族 | AF_INET:ipv4 AF_INET6:ipv6 |
type | 指定套接字类型 | SOCK_STREAM:提供有序的、可靠的、双向的、基于连接的字节流,流式协议。 SOCK_DGRAM:固定长度的、无连接的、不可靠的报文传递,报式协议 |
protocol | 具体的一个协议 | 一般写0 流式协议默认是TCP 报式协议默认是UDP |
绑定端口bind
- 函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 函数解析
作用 :将socket创建的socketfd与地址addr进行绑定。大部分用来绑定端口。一般在于服务端使用,如果客户端在需要绑定端口的情况下,也可以使用。
例如将刚刚创建的socketfd与本地端口4017进行绑定
struct sockaddr_in socket_addr;
memset(&socket_addr, 0, sizeof(socket_addr));
socket_addr.sin_family = AF_INET;
socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket_addr.sin_port = htons(4017);
bind(socketfd, (struct sockaddr *)&socket_addr, sizeof(socket_addr));
返回:成功返回0,一般端口被其他进程占用时,才会返回失败!
- 参数解析
参数 | 说明 |
---|---|
sockfd | 创建的套接字 |
sockaddr | 地址,结构体中包含了协议族,端口号以及地址ip |
socklen_t | 地址所指向的结构体的地址长度 |
进入监听状态listen
- 函数原型
int listen(int sockfd, int backlog);
- 函数解析
作用 :让服务端进入监听状态,等待客户端的连接。用于服务端的进程。需在accept之前使用
例如 最大监听客户端数量为5
listen(socketfd, 5);
返回:
成功返回0
- 参数解析
参数 | 说明 |
---|---|
sockfd | 创建的套接字 |
backlog | 服务端等待连接的最大数量(即半连接和全连接最大之和) |
获取客户端连接请求accept
- 函数原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 函数解析
作用 :获取客户端的连接请求并建立连接
例如
struct sockaddr_in client_addr;
int clilen = sizeof(struct sockaddr_in);
int clifd = accept(socketfd, (struct sockaddr*)&client_addr, (socklen_t*)&clilen);
返回:成功返回客户端的socketfd,后期通过该socketfd与此客户端进行通信。失败返回-1
- 参数解析
参数 | 说明 |
---|---|
sockfd | 用于监听的文件描述符 |
addr | 用于返回监听到的客户端的地址信息 |
addrlen | addr的字节大小 |
接收网络数据read
- 函数原型
ssize_t read(int fd, void *buf, size_t count);
- 函数解析
作用 :接收网络数据
例如获取从clientfd中接收到的网络数据
char buf[256] = {0};
read(clientfd, buf, 256);
返回:成功返回接收到的网络数据长度;失败返回<0。
头文件:unistd.h
- 参数解析
参数 | 说明 |
---|---|
sockfd | 需要获取数据的对象socketfd |
buf | 接收到的网络数据 |
count | 限制字节接收长度。 当缓冲区长度大于该长度时,返回count长度字节; 档缓冲区长度小于该长度时,返回实际长度的字节 |
发送数据write
- 函数原型
ssize_t write(int fd, const void *buf, size_t count);
- 函数解析
作用 :用于发送数据
例如 :对clientfd发送abc
char sendbuf[] = “hello”;
write(clientfd, sendbuf, 3);
返回:成功返回成功写入的字节数,失败返回-1
头文件:unistd.h
- 参数解析
参数 | 说明 |
---|---|
fd | 需要发送数据的对象socketfd |
buf | 发送的数据 |
count | 当实际发送数据长度大于count时,按count数量字节数发送,否则按实际长度发送 |
读取和写入除了read和write外,还有recv和send;recvmsg和sendmsg等。
关闭套接字close
- 函数原型
int close(int fd);
- 函数解析
作用 :关闭指定套接字
例如
close(clientfd);
返回:成功返回0
- 参数解析
参数 | 说明 |
---|---|
fd | 需要关闭的套接字 |
连接指定服务端connect
- 函数原型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
注意 客户端使用
- 函数解析
作用 :在tcp中用于跟服务端建立连接,而在udp中仅仅是在本地对服务端的IP地址和端口与创建的sockfd进行绑定记录
例如 连接ip地址为127.0.0.1(本地回环),且端口号为4017的服务端进程
struct sockaddr_in socket_addr;
memset(&socket_addr, 0, sizeof(socket_addr));
socket_addr.sin_family = AF_INET;
socket_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
socket_addr.sin_port = htons(4017);
connect(sockfd, (struct sockaddr *)&socket_addr,sizeof(socket_addr));
返回:成功返回0,失败返回-1
inet_addr所在头文件为:arpa/inet.h
- 参数解析
参数 | 说明 |
---|---|
sockfd | 用于通信的文件描述符 |
addr | 客户端所要连接的服务端的地址信息 |
addrlen | addr的内存大小 |