本片博客主要介绍TCP编程,包括TCP常用函数的介绍,以及实例演示
1、函数说明
在编程总常用的基本函数有socket()、bind()、listen()、accept()、send()、sendto()、recvfrom()等,下面开始介绍这些函数的功能以及使用方法:
soscket():该函数用于创建一个套接字,同时指定协议与类型。socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行读写。
所需头文件 | #include<sys/socket.h> #include <sys/types.h> |
||
函数原型 | int socket(int domain, int type, int protocol); | ||
函数传入值 | domain:协议族 | AF_INET:IPv4协议 | |
AF_INET6:IPv6协议 | |||
AF_LOCAL:UNIX域协议 | |||
AF_ROUTE:路由套接字 | |||
AF_KEY:密钥套接字 | |||
type:套接字类型 | SOCK_STREAM:流式套接字 | ||
SOCK_DGRAM:数据报套接字 | |||
SOCK_RAW:原始套接字 | |||
protocol:0(原始套接字除外) | |||
函数返回值 | 成功:非负套接字描述符 | ||
错误:-1 |
bind():该函数将保存在相应地址结构中的地址信息和套接字进行绑定。它主要用于服务器端,客户端创建的套接字可以不绑定地址。绑定时一般需要指定IP地址和端口号,否则内核会随意分配一个临时端口给套接字。IP地址可以直接指定0本机的IP地址(如inet_addr("192.168.1.123")),或者使用宏INADDR_ANY,允许将套接字与服务器的任意网络接口(如eth0、eth0:1、eth1等)进行绑定。
所需头文件 | #include <sys/types.h> |
函数原型 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
函数传入值 | sockfd:套接字描述符 |
addr:绑定的地址 | |
addrlen:地址长度 | |
函数返回值 | 成功:0 |
错误:-1 |
listen():在服务端程序成功建立套接字并于地址进行绑定之后,通过调用listen函数将套接字设置成监听模式(被动模式),准备接受客户端的连接请求。
注意:backlog参数,服务器在调用listen和accept后,就会阻塞在accept函数上,accpet函数返回后循环调用accept函数等待客户的TCP连接。如果这时候又大量的用户并发发起connect连接,那么在listen有队列上限(最大可接受TCP的连接数)的情况下,有多少个connect会成功了。试验证明,当连接数远远高于listen的可连接数上限时,客户端的大部分TCP请求会被抛弃,只有当listen监听队列空闲或者放弃某个连接时,才可以接收新的连接。
参考:https://blog.csdn.net/sukhoi27smk/article/details/11903415
所需头文件 | #include<sys/socket.h> |
函数原型 | int listen(int sockfd, int backlog); |
函数传入值 | sockfd:套接字描述符 |
backlog:请求队列中允许的最大请求,大多数系统默认为5 | |
函数返回值 | 成功:0 |
错误:-1 |
accept():服务器端通过调用accept()函数等待并接受客户端的连接请求。建立好TCP连接之后,该函数会返回一个已连接的套接字。返回值是一个fd,accept正确返回就表示我们已经和前来连接我的客户端之间建立了一个TCP连接了,以后我们就要通过这个连接来和客户端进行读写操作,读写操作就需要一个fd,这个fd就由accept来返回了。
所需头文件 | #include<sysy/socket.h> |
函数原型 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
函数传入值 | sockfd:套接字描述符 |
addr:用于保存客户端地址 | |
addrlen:地址长度 | |
函数返回值 | 成功:建立好连接的套接字描述符 |
错误:-1 |
connect():客户端通过该函数向服务器端的监听套接字发送请求
所需头文件 | #include<sys/socket.h> |
函数原型 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
函数传入值 | sockfd:套接字描述符 |
addr:服务器端地址 | |
addrlen:地址长度 | |
函数返回值 | 成功:0 |
错误:-1 |
send()和recv():这两个函数通常在TCP通信过程中用于发送和接收数据,也可用在UDP中。
所需头文件 | #include |
函数原型 | ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
函数传入值 | sockfd:套接字描述符 |
buf:发送缓冲区的地址 | |
flags:一般为0 | |
len:发送数据的长度 | |
函数返回值 | 成功:实际发送的字节数 |
错误:-1 |
所需头文件 | #include |
函数原型 | ssize_t recv(int sockfd, const void *buf, size_t len, int flags); |
函数传入值 | sockfd:套接字描述符 |
buf:存放接收数据的缓冲区 | |
flags:一般为0 | |
len:接收数据的长度 | |
函数返回值 | 成功:实际接收到的字节数 |
错误:-1 |
sendto()和recvfrom():这两个函数通常在UDP通信过程中用于发送和接收数据。当用在TCP时,后面的几个与地址有关的参数不起作用,函数作用等同于send()和recv()
所需头文件 | #include<sys/socket.h> |
函数原型 | ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); |
函数传入值 | sockfd:套接字描述符 |
buf:发送缓冲区首地址 | |
len:发送数据的长度 | |
flags:一般为0 | |
dest_addr:接收方的IP地址和端口号 | |
addrlen:地址长度 | |
函数返回值 | 成功:实际发送的字节数 |
错误:-1 |
所需头文件 | #include<sys/socket.h> |
函数原型 | ssize_t recvfrom(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *src_addr, socklen_t addrlen); |
函数传入值 | sockfd:套接字描述符 |
buf:接收缓冲区首地址 | |
len:接收数据的长度 | |
flags:一般为0 | |
dest_addr:发送方的IP地址和端口号 | |
addrlen:地址长度 | |
函数返回值 | 成功:实际接收的字节数 |
错误:-1 |
3、编程示例
示例分两部分,一部分是服务器,一部分是客户端
流程图如下:
/*
server.c
*/
#include <stdio.h>
#include <strings.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define BUF_SIZE 128
int main(int argc, char *argv[])
{
int sockfd, connfd;
struct sockaddr_in servaddr, cliaddr;
char buf[BUF_SIZE] = {0};
socklen_t peerlen;
if(argc < 3)
{
printf("Usage:%s <IP> <port>\n", argv[0]); //提示输入
exit(-1);
}
/*建立socket连接*/
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(-1);
}
printf("sockfd = %d\n", sockfd);
/*设置soscket_in结构体中相关参数*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2])); //把字符串转换成整型数,然后转换成对应的网络字节序
servaddr.sin_addr.s_addr = inet_addr(argv[1]); //sin_addr这个结构体中只有一个元素
/*绑定函数bind(),将socket描述符与本地的Ip与端口绑定*/
if(bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
perror("bind");
exit(-1);
}
printf("bind success\n");
/*调用listen()函数,设置监听模式*/
if(listen(sockfd, 10) < 0)
{
perror("listen");
exit(-1);
}
printf("listen success\n");
/*调用accept()函数,等待客户端的连接*/
peerlen = sizeof(cliaddr);
while(1)
{
if((connfd = accept(sockfd, (struct sockaddr*)&cliaddr, &peerlen)) < 0) //程序会阻塞在accept这里,直到有客户端来连接
{
perror("accept");
exit(-1);
}
printf("connect success\n");
/*调用recv()函数接收客户端发送的数据*/
memset(buf, 0, sizeof(buf));
if(recv(connfd, buf, BUF_SIZE, 0) == -1)
{
perror("recv");
exit(-1);
}
printf("Receve a message: %s", buf);
strcpy(buf, "welcome to server\n");
send(connfd, buf, BUF_SIZE, 0);
close(connfd);
}
close(sockfd);
exit(0);
}
/*
client.c
*/
#include <stdio.h>
#include <strings.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define BUF_SIZE 128
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
char buf[BUF_SIZE] = {"hello Server"};
if(argc < 3)
{
printf("Usage:%s <IP> <port>\n", argv[0]); //提示执行程序是 传递的参数
exit(-1);
}
/*建立socket*/
if(sockfd = socket(AF_INET, SOCK_STREAM, 0) < 0)
{
perror("socket");
exit(-1);
}
printf("listenfd = %d\n", sockfd);
/*设置soscket_in结构体中相关参数*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2])); //把字符串转换成整型数,然后转换成对应的网络字节序
servaddr.sin_addr.s_addr = inet_addr(argv[1]); //sin_addr这个结构体中只有一个元素
if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) //程序会阻塞在accept这里,直到有客户端来连接
{
perror("connect");
exit(-1);
}
send(sockfd, buf, BUF_SIZE, 0);
/*调用recv()函数接收客户端发送的数据*/
if(recv(sockfd, buf, BUF_SIZE, 0) == -1)
{
perror("recv");
exit(-1);
}
printf("Receve a message: %s", buf);
close(sockfd);
exit(0);
}
运行结果