文章目录
在网络编程中客户端和服务端所需要的函数如下图:
1.创建socket
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
这个函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1。
domain
函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。
如下,domain的值及含义
type
函数socket()的参数type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。
如下type的值及含义
并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。
protocol
函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
类型为SOCK_STREAM的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用connect()函数进行。一旦连接,可以使用read()或者write()函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内任然没有接受完毕,可以将这个连接人为已经死掉。
SOCK_DGRAM和SOCK_RAW 这个两种套接字可以使用函数sendto()来发送数据,使用recvfrom()函数接受数据,recvfrom()接受来自制定IP地址的发送方的数据。
SOCK_PACKET是一种专用的数据包,它直接从设备驱动接受数据。
errno
函数socket()并不总是执行成功,有可能会出现错误,错误的产生有多种原因,可以通过errno获得:
如下 errno的值及含义
示例,建立一个流式套接字:
int sock = socket(AF_INET, SOCK_STREAM, 0);
2.命名socket
socket() 函数用来创建套接字,确定套接字的各种属性,然后服务器端要用 bind() 函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理;而客户端要用 connect() 函数建立连接。
sockaddr_in
这个结构体用来处理网络通信的地址。
bind
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
-sock:文件描述符
-addr:sockaddr结构体变量的指针
-addrlen:addr变量的大小,可用sizeof()计算
返回值:
代码演示:
将创建的套接字与IP地址127.0.0.1、端口8888绑定。
//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, 0);
//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
serv_addr.sin_port = htons(8888); //端口
//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
-inet_addr 将字符串形式的IP地址 -> 网络字节顺序 的整型值
-inet_ntoa 网络字节顺序的整型值 ->字符串形式的IP地址
-网络字节顺序与本地字节顺序之间的转换函数:
htonl()–“Host to Network Long”
ntohl()–“Network to Host Long”
htons()–“Host to Network Short”
ntohs()–“Network to Host Short”
connect()
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
3.监听socket
socket被命名之后还不能马上接受客户的连接,需要创建一个监听队列存放待处理的客户连接。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
-sockfd:指定被监听的socket
-backlog:提示内核监听队列的最大长度。监听队列的长度如果超过backlog,服务器将不受理新的客户端连接,客户端也将收到ECONNREFUSED错误信息。
返回值:成功返回0,失败返回-1
请求队列:当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。
4.接收连接
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr, socklen *addrlen);
-sockfd:执行过listen系统调用的监听socket
-addr:获取被接受连接的远端socket地址
-返回值:失败返回-1
accpet()成功时返回一个新的连接socket,该socket唯一的表示了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信。
5.发起连接(暂时了解)
客户端通过connect调用主动的与服务器建立连接。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
6.关闭连接
close()
#include <unistd.h>
int close(int fd);
-fd:待关闭的socket的描述符
将fd的引用计数减一只有当引用计数为0的时候才真正的关闭连接。一次fock将是的父进程当中打开的socket引用计数加一。因此必须都关闭才会真正的将连接关闭。
showdown()
调用立即终止连接。
7.TCP数据读写
#include <sys/types.h>
#include <sys/socket.h>
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);
8.UDP数据读写(了解)
9.实现多线程TCP服务器
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<string.h>
#include<pthread.h>
void* thread_start(void* arg){
int client = *(int*)arg;
//保存接收到的数据
char buf[1024] = { 0 };
for (;;){
int recvlen = recv(client, buf, sizeof(buf)-1, 0);
if (recvlen <= 0)break;
buf[recvlen] = '\0';
if (strstr(buf, "quit") != NULL){
char re[] = "quit success!\n";
send(client, re, strlen(re) + 1, 0);
break;
}
int sendlen = send(client, "ok\n", 4, 0);
printf("recv %s\n", buf);
}
close(client);
}
int main(int argc, char* argv[]){
//创建socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1){
printf("create socket failed!\n");
return -1;
}
//绑定一个端口
unsigned short port = 9999;
if (argc > 1){
port = atoi(argv[1]);//将字符串转换为一个整数
}
sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);//将主机字节顺序转换为网络字节顺序【默认大端】(处理大端小端问题)
saddr.sin_addr.s_addr = htonl(0);
if (bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){
printf("bind port %d failed!\n", saddr.sin_port);
return -2;
}
printf("bind port %d success!\n", port);
listen(sock, 10);//在一段时间内最大可以让多少个链接进来
for (;;){
//接受客户端请求
sockaddr_in caddr;
socklen_t len = sizeof(caddr);
//int client=accept(sock, 0, 0);
int client = accept(sock, (sockaddr*)&caddr, &len);
if (client <= 0)break;
printf("accept client %d \n", client);
char* ip = inet_ntoa(caddr.sin_addr);
unsigned short cport = ntohs(caddr.sin_port);
printf("client ip is %s,port is %d\n", ip, cport);
//创建线程
pthread_t tid;
pthread_create(&tid, NULL, thread_start, (void*)&client);
//将主线程与子线程分离,子线程执行完毕后自动释放资源
pthread_detach(tid);
}
close(sock);
return 0;
}