网络编程:编写程序使两台联网的计算机相互交换数据。
socket:被翻译为套接字,它是计算机之间约定好的一种通信方式,是用来连接到因特网的工具。典型的应用:浏览器(客户端)获取用户输入的 URL,向服务器发起请求,服务器分析接收到的 URL,将对应的网页内容返回给浏览器(客户端),浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。
文件描述符:在Linux中一切都是文件,为了区分打开的文件,Linux都会给每个文件分配一个id整数,这个id就是文件描述符,而在Linux中,网络连接也是一个文件,它也有文件描述符,当我们在创建socket()时,会返回一个文件描述符,有了这个文件符我们就可以像普通文件一样write()、read()等文件操作。
socket有很多种数据传输方式,这里介绍基本的两种方式:
- 流格式套接字(Stream Sockets)就是“面向连接的套接字”,它基于 TCP 协议,它是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。
- 数据报格式套接字(Datagram Sockets)就是“无连接的套接字”,它基于 UDP 协议,它是数据报套接字是一种不可靠的、不按顺序传递的、以追求速度为目的的套接字。
编写客户端一般基本流程:
- 创建套接字socket()
- 向服务器发起请求,连接服务器connect()
- 接受或者向服务器发送信息write()/read()
- 关闭套接字close()
编写服务器一般基本流程:
- 创建套接字socket()
- 绑定套接字bind()
- 监听listen()
- 接受客户端的请求accept()
- 接受或者向客户端发送信息write()/read()
- 关闭套接字close()
int socket(int af, int type, int protocol); //创建,返回一个int类型的文件描述符,失败返回-1
- af:ip地址类型, 常用的有AF_INET、AF_INET6,表示ipv4或者ipv6类型
- type:数据传输类型,SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)
- protocol: IPPROTO_TCP 和 IPPTOTO_UDP,表示tcp方式或者udp方式,有时填0,让系统根据type类型自动选择。
int bind(int sock, struct sockaddr *addr, socklen_t addrlen); //绑定,
- sock:创建socket()时的文件描述符
- addr:sockaddr 结构体变量的指针,存放IP地址类型、IP地址、端口号等信息
- addrlen:sockaddr结构体的大小,可由sizeof()得到该值
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); //连接服务器
- sock:创建socket()时的文件描述符
- addr:sockaddr 结构体变量的指针,存放IP地址类型、IP地址、端口号等信息
- addrle:sockaddr结构体的大小,可由sizeof()得到该值
int listen(int sock, int backlog); //监听
- sock:创建socket()时的文件描述符
- backlog:请求队列的最大长度,可以根据你的需求来定,并发量小的话可以是10或者20,如果设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); //接收客户端,返回一个新的文件描述符来和客户端通信
- sock:创建socket()时的文件描述符
- addr:sockaddr 结构体变量的指针,存放IP地址类型、IP地址、端口号等信息
- addrle:sockaddr结构体的大小,可由sizeof()得到该值
ssize_t write(int fd, const void *buf, size_t nbytes); //写入数据
- fd:创建socket()时的文件描述符,或者accept()的文件描述符
- buf:为要写入的数据的缓冲区地址
- nbytes:要写入的数据的字节数
ssize_t read(int fd, void *buf, size_t nbytes); //读出数据
- fd:创建socket()时的文件描述符,或者accept()的文件描述符
- buf:为要写入的数据的缓冲区地址
- nbytes:要写入的数据的字节数
实现客户端和服务器端的demo:
-----------------------------------客户端------------------------------------
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(int argc,char *argv[])
{
int sock = 0;
char buff[64] = {0};
char str[] = "hello,is me client";
struct sockaddr_in serv_addr;
sock = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址,127.0.0.1一般指的是本机地址
//端口,Web 服务的端口号是 80,FTP 服务的端口号是 21,SMTP 服务的端口号是 25,我们的程序要尽量在 1024~65536 之间分配端口号
serv_addr.sin_port = htons(2048);
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //连接服务器
read(sock, buff, sizeof(buff));
printf("Message form server: %s\n", buff);
write(sock, str, sizeof(str));
close(sock); //关闭套接字
return 0;
}
-----------------------------------服务器端--------------------------------------
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc,char *argv[])
{
int serv_sock = 0; //创建套字的文件描述符
int clnt_sock = 0; //接收客户端请求的文件描述符
char buff[64];
char str[] = "hello,is me server";
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址,127.0.0.1一般指的是本机地址
//端口,Web 服务的端口号是 80,FTP 服务的端口号是 21,SMTP 服务的端口号是 25,我们的程序要尽量在 1024~65536 之间分配端口号
serv_addr.sin_port = htons(2048);
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //将套接字和IP、端口绑定
listen(serv_sock, 20); //进入监听状态,等待用户发起请求
//接收客户端请求,会阻塞程序执行(后面代码不能被执行),直到有新的请求到来
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
write(clnt_sock, str, sizeof(str));
read(clnt_sock, buff, sizeof(buff));
printf("Message form client: %s\n", buff);
//关闭套接字
close(clnt_sock);
close(serv_sock);
return 0;
}
程序的执行结果如下: