tcp协议的基本情况在此就不详细介绍,上图是《UNIX网络编程》书里的一张图,此图直观的展现了tcp服务器和客户是如何开始通信、进行通信和结束通信的全过程。
首先介绍Ipv4套接字地址结构:
struct in_addr {
in_addr_t s_addr;//32位 ipv4的地址存放在这
};
struct sockaddr_in {
__uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_len是为增加对OSI协议的支持而随4.3BSD-Reno添加的。
sin_family是一个无符号短整数,使用时可以按照不同的规范选用。
sin_port是端口号。
sin_addr是一个结构体,该结构体内保存了ipv4地址。
sin_zero[8]字段未曾被使用过,不过在填写这种套接字地址结构时,总是把它置为0。
为了让套接字函数的输入适应不同协议族(ipv4、ipv6等),提供了通用套接字。
通用套接字地址结构:
struct sockaddr {
__uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
在使用bind()、accept()等函数时需要把Ipv4套接字地址结构强制转化成通用套接字然后带入函数。例如:
int bind(int,struct sockaddr *,socklen_t);
socket() 函数:
socket()函数用于创建套接字,socket()函数成功时返回一个类似于文件描述符的非负整数,我们称之为套接字描述符。
int socket(int family, int type, int protocol);
成功返回非负数描述符,失败返回-1
其中:
family参数指明协议族,ipv4一般用AF_INET。
type参数指明套接字类型,有四种类型分别是字节流套接字、 数据报套接字、有序分组套接字、原始套接字。这里我们用字节流套接字,SOCK_STREAM。对另外几种感兴趣的小伙伴可以自己百度一下。
Protocol参数是协议类型,有tcp传输协议、udp传输协议、sctp传输协议。也可以默认为0,以family和type的组合,系统自己赋值。
connect() 函数:
connect()函数用于通信中的主动方向被动方发起建立连接的请求。对于TCP套接字来说,connect()函数会激发TCP的三次握手。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
成功返回0,失败返回-1
sockfd:套接字描述符,由socket()函数返回的。
addr:指向套接字地址结构的指针。
addrlen:套接字地址结构的大小。
bind() 函数:
bind()函数用于将本地协议地址与socket()函数创建的套接字绑定起来。
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
成功返回0,失败返回-1。
sockfd: 套接字描述符,由前面的socket()函数成功时返回。
myaddr: 指向特定于协议的地址结构的指针。
addrlen: 该地址结构的长度。
listen()函数:
listen()函数由TCP服务器调用,主要有两个作用:
1. 将一个未连接的套接字转换为被动套接字,指示内核应该接 收指向该套接字的连接请求。
2. 指定内核应该为相应的套接字队列的最大连接个数。
int listen(int sockfd, int backlog);
成功返回0,失败返回-1。
sockfd: 套接字描述符,由前面的socket()函数成功时返回。
backlog: 指定内核应该为相应的套接字排队的最大连接个数。
accept()函数:
accept()函数由TCP服务器调用,用于返回下一个已完成连接。如果已完成连接队列为空,则阻塞。
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
成功则返回套接字描述符,失败返回-1
sockfd: 内核创建的新的套接字描述符,用于描述与返回的客户端之间的连接
cliaddr: 已连接的对端客户对协议地址。
addrlen: 该地址结构的长度。
fork()函数:
pid_t fork(void);
返回:两次返回在子进程中返回0,在父进程中为子进程的id,若出错则为-1。
生成一个和父进程一样的子进程,代替父进程执行下面的操作,解放父进程。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int ser_socket;
struct sockaddr_in se_addr,cl_addr;
int addr_len = sizeof(cl_addr);
int client;
char buffer[4000],renew[4000];
int datanum;
if ((ser_socket = socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
return -1;
}
bzero(&se_addr, sizeof(se_addr));
se_addr.sin_family = AF_INET;
se_addr.sin_port = htons(5901);
se_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(ser_socket,(struct sockaddr *)&se_addr,sizeof(se_addr))<0)
{
perror("bind");
return -1;
}
if(listen(ser_socket, 10)<0)
{
perror("listen");
return -1;
}
//等待请求
while (1) {
printf("Listening on port :%d\n",5901);
client = accept(ser_socket, (struct sockaddr*)&cl_addr, (socklen_t *)&addr_len);
if(client<0)
{
perror("accept");
continue;
}
printf("\n recv client data:\n");
printf("IP is %s\n",inet_ntoa(cl_addr.sin_addr));
printf("port is %d\n",htons(cl_addr.sin_port));
//进行对话,创建线程
if((pid = fork())==0)
{
close(ser_socket);
while(1)
{
datanum = recv(client, buffer, 4000, 0);// 收数据
if(datanum< 0)
{
perror("recv");
continue;
}
buffer[datanum] = '\0';
printf("%d:say %s\n", htons(cl_addr.sin_port), buffer);
printf("you want to say:");
scanf("%s",renew);
send(client, renew, strlen(renew), 0);
if(strcmp(renew, "quit") == 0)
{
exit(0);//销毁此线程
break;
}
}
}
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, const char * argv[]) {
int client_socket;
struct sockaddr_in ser_addr;
char sendbuf[400];
char recbuf[400];
int sennum,recnum;
if((client_socket= socket(AF_INET, SOCK_STREAM, 0))<0)
{
perror("socket");
return -1;
}
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(5901);
ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(client_socket, (struct sockaddr *)&ser_addr, sizeof(ser_addr)) < 0)
{
perror("connect");
return 1;
}
printf("connect with destination host...\n");
while(1)
{
printf("Input your world:>");
scanf("%s", sendbuf);
printf("\n");
send(client_socket, sendbuf, strlen(sendbuf), 0);
recnum = recv(client_socket, recbuf, 4000, 0);
recbuf[recnum] = '\0';
printf("recv is: %s\n", recbuf);
if(strcmp(recbuf, "quit") == 0)
break;
}
close(client_socket);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, const char * argv[]) {
int client_socket;
struct sockaddr_in ser_addr;
char sendbuf[400];
char recbuf[400];
int sennum,recnum;
if((client_socket= socket(AF_INET, SOCK_STREAM, 0))<0)
{
perror("socket");
return -1;
}
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(5901);
ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(client_socket, (struct sockaddr *)&ser_addr, sizeof(ser_addr)) < 0)
{
perror("connect");
return 1;
}
printf("connect with destination host...\n");
while(1)
{
printf("Input your world:>");
scanf("%s", sendbuf);
printf("\n");
send(client_socket, sendbuf, strlen(sendbuf), 0);
recnum = recv(client_socket, recbuf, 4000, 0);
recbuf[recnum] = '\0';
printf("recv is: %s\n", recbuf);
if(strcmp(recbuf, "quit") == 0)
break;
}
close(client_socket);
return 0;
}
总结:《unix网络编程》不仅仅适用于unix系统的网络编程,也讲述了很多通用性的内容,强力安利一波。自己写的这个简单的多并发服务器是个基于tcp的多并发聊天程序。先运行服务器端,然后再运行客户端,服务器端可以与客户1、2一起聊天,不用等待某一客户的回复。
代码测试环境是mac 10.13.1,本人不才,若文中有错误,恳请大家积极指正。