文章目录
在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。
在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。
IPv4协议,那时候都使用的是sockaddr结构体
,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in
还是其他的,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。
socket函数
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。
需要头文件:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
int sock;//代表信箱
struct sockaddr_in server_addr; // 便签服务器的地址
//1.创建信箱
sock = socket(AF_INET, SOCK_STREAM, 0);
其中 “int domain”参数表示套接字要使用的协议簇,协议簇的在“linux/socket.h”里有详细定义,常用的协议簇:
domain参数:
- AF_INET:(TCP/IP – IPv4)
- AF_INET6:(TCP/IP – IPv6)
- AF_UNIX:(本机通信)使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type参数
- SOCK_STREAM(TCP流)
- SOCK_DGRAM(UDP数据报)
- SOCK_RAW(原始套接字)
protocol参数
- 传0 表示使用默认协议。
返回值:
成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
bind 函数
头文件
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。
bind()的作用是将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。
bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- sockfd:socket文件描述符
- backlog:在Linux 系统中,它是指排队等待建立3次握手队列长度
//把信箱挂置到传达室,这样,就可以接收信件了
listen(sock, 128);
查看系统默认backlog
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
改变 系统限制的backlog 大小
vim /etc/sysctl.conf
最后添加
net.core.somaxconn = 1024
net.ipv4.tcp_max_syn_backlog = 1024
保存,然后执行
sysctl -p
accept 函数
在使用套接字(socket)进行网络编程时,在调用 listen() 函数后,需要通过 accept() 函数来接收客户端的连接请求,并创建一个新的 socket 文件描述符来专门处理该客户端的通信。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockdf:socket文件描述符
- addr:传出参数,返回链接客户端地址信息,含IP地址和端口号
- addrlen:传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
- 返回值:成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,
int sock;//代表信箱
struct sockaddr_in server_addr; // 便签服务器的地址
//1.创建信箱
sock = socket(AF_INET, SOCK_STREAM, 0);
...
struct sockaddr_in client;
int client_sock
client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
三次握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
connect函数
客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。
connect()用于建立连接。accept()用于使服务器等待来自某客户进程的实际连接。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockdf:socket文件描述符
- addr:传入参数,指定服务器端地址信息,含IP地址和端口号
- addrlen:传入参数,传入sizeof(addr)大小
- 返回值:成功返回0,失败返回-1,设置errno
send()与recv()
send:是一个系统调用函数,用来发送消息到一个套接字中
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:向套接字中发送数据
参数:
- ssize_t类型在之前的read/write中描述符哦,相当于long。
- sockfd:接收消息的套接字的文件描述符。
- buf:要发送的消息。
- len:要发送的字节数。
- flags:flags参数表示下列标志中的0个或多个
设置为0时 功能和write一样
send和write的唯一区别就是最后一个参数:flags的存在,当我们设置flags为0时,send和wirte是同等的。
返回值:成功返回实际发送的字节数,失败:返回 -1
struct sockaddr_in client;
int client_sock, len, i;
char client_ip[64];
char buf[256];
string message("start");
socklen_t client_addr_len;
client_addr_len = sizeof(client);
client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
send(client_sock, str, strlen(str) + sizeof(char), NULL);
send(sockfd, (const void *)message.c_str(), message.length(),0);
recv()函数
函数原型:
ssize_t recv(int sockfd, const void *buf, size_t len, int flags);
功能:向套接字中发送数据
参数:
功能:不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
参数一:指定接收端套接字描述符;
参数二:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
参数三:指明buf的长度;
参数四 :一般置为0。设置为0和read功能相同
失败:返回 -1
read()/write()
包含头文件
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
-
int fd:该参数指明从文件描述符fd的缓冲区读/写。
-
void *buf
:read()函数读到的数据将保存到buf所指向的空间中,该参数为void*(无类型指针)表明用户可以传入任何类型的指针,一般我们可以传入char型数组或指针来接收数据,但是如何接收int类型的数据,我目前还没有办法,欢迎大佬指点~。 -
size_t count
这参数限制了读取/写入的字节数,即如果读缓冲区有20字节数据,如果调用read()时count设置为10,则只会返回10个字节数据。如果读缓冲区有7字节数据,如果调用read()时count设置为10,则read()函数会正常返回7字节的数据,而不会出现错误。
-
const void *buf
这里加const表明指向常量的指针。 -
返回值
成功将返回读取/写入的字节数,返回的数小于请求的字节数,也属于成功返回;size_t 和 ssize_t 分别表示无符号整型数据和有符号整型数据。
错误返回-1,
read()和write()函数并不是专门为socket编程设计的,socket编程读写操作推荐用send()/recv()函数。而read()和write()适用于所有的文件读写,因为Linux下一切皆文件,所以每个套接字都可以看作一个文件,也可以使用read()和write()函数进行读写操作。
建立socket服务器代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#define SERVER_PORT 666
int main(void)
{
int sock;//代表信箱
struct sockaddr_in server_addr; // 便签服务器的地址
//1.创建信箱
sock = socket(AF_INET, SOCK_STREAM, 0);
//2.清空标签,写上地址和端口号
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;//选择协议族IPV4
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址
server_addr.sin_port = htons(SERVER_PORT);//绑定端口号8900
//实现标签贴到收信得信箱上
bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
//把信箱挂置到传达室,这样,就可以接收信件了
listen(sock, 128);
//万事俱备,只等来信
printf("等待客户端的连接\n");
// 一个死循环,每次只处理一次连接
int done =1;
while(done)
{
// 创建客户端
struct sockaddr_in client;
int client_sock, len, i;
char client_ip[64];
char buf[256];
char c_test[1024] = {0};
socklen_t client_addr_len;
client_addr_len = sizeof(client);
client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
printf("连接成功\n");
// 向客户端发送c_test字符串
send(client_sock, c_test, strlen(str) + sizeof(char), NULL);
close(client_sock);
}
close(sock);
}
建立socket客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERVER_PORT 666
#define SERVER_IP "127.0.0.1"
int main(int argc, char *argv[]){
int sockfd;
char *message;
struct sockaddr_in servaddr;
int n;
char buf[64];
if(argc != 2){
fputs("Usage: ./echo_client message \n", stderr);
exit(1);
}
message = argv[1];
printf("message: %s\n", message);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, '\0', sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
servaddr.sin_port = htons(SERVER_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
write(sockfd, message, strlen(message));
n = read(sockfd, buf, sizeof(buf)-1);
if(n>0){
buf[n]='\0';
printf("receive: %s\n", buf);
}else {
perror("error!!!");
}
printf("finished.\n");
close(sockfd);
return 0;
}