印象笔记连接:网络编程 第二天 (TCP编程)
在网络应用程序编程时,服务器与客户机的运行逻辑不同,因此服务器端与客户端的代码也不同。
一、TCP编程——服务器端
使用TCP通信的服务器端的编程流程如下:
如果需要使用TCP协议创建一个服务器,则需要以下步骤:
-创建套接字
-绑定套接字
-设定监听模式
-接收客户机的连接请求
-接收/发送数据
-断开连接
1、创建一个套接字
在网络通信中通常使用套接字socket进行通信。若需要进行网络通信则必须创建一个套接字。使用socket()函数创建一个套接字。
socket()函数的用法:
函数socket()
所需头文件:#include<sys/types.h>
#include<sys/socket.h>
函数原型:int socket(int domain, int type, int protocol)
函数参数:
domain 选择通信协议。通信协议族定义在头文件socket.h中。常用的协议有:
AF_INET IPv4通信协议
AF_INET6 IPv6通信协议
AF_UNIX 本地通信
//其他取值见man帮助文档和头文件socket.h内容
type 套接字类型。常见的值有:
SOCK_STREAM 流式套接字,TCP协议选用该套接字类型
SOCK_DGRAM 数据报套接字,UDP协议选用该套接字类型
SOCK_RAW 原始套接字,IP/ICMP协议选用该套接字类型
protocol 协议值,通常情况下为0,即默认IP协议;其他协议值通过查看:/user/include/linux/in.h
函数返回值:
成功:非负套接字文件描述符
失败:-1
使用scoket()创建套接字是网络编程的第一步,也是最重要的一步,后续的所有网络通信编程函数都是基于套接字进行操作的。
当调用socket()之后,内核内部生成一个socket结构体,并返回该结构体的文件描述符作为返回值以便后续代码进行操作。
注意:套接字在Linux内核中是一个结构十分复杂的结构体,使用socket()创建一个套接字会在内核里生成一个套接字结构体并返回该套接字结构体的文件描述符。因此绝对不可以使用未获得socket()返回值的普通的int类型变量进行后续编程操作。
2、绑定套接字
使用socket()创建套接字后,该套接字虽然存在但并未给它分配地址,还需将该套接字与指定的IP地址和端口号进行绑定。使用bind()给套接字分配地址(IP地址和端口号)。
bind()函数的用法:
函数bind()
所需头文件:#include<sys/types.h>
#include<sys/socket.h>
函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
函数参数:
sockfd 需要绑定的套接字的文件描述符
addr 绑定的地址。该参数是一个结构体指针,必须使用地址传递的方式传参。需要针对不同的协议设定不同的结构体类型,常见的结构体如下:
struct sockaddr //通用地址结构体;具体情况下自己还要进行替换
{
sa_family_t sa_family; //地址族,值为AF_XXX
char sa_data[14]; //协议地址
}
struct sockaddr_in //IPv4协议地址结构体,这个才是我们要用到的结构体
{
sa_family_t sin_family; //地址族,固定值AF_INET, IPv4
in_port_t sin_port; //端口号,通常使用htons()计算获得
struct in_addr sin_addr; //IP地址,具体结构体见下
}
在结构体sockaddr_in中,第三个成员sin_addr是一个in_addr类型的结构体,其结构体如下:
struct in_addr
{
uint32_t s_addr; //IP地址,为32位无符号数,通常使用inet_addr()计算获得
}
addrlen 地址长度。值为第二个参数addr的结构体长度,通常为sizeof(sockaddr_in)
函数返回值:
成功:0
失败:-1
绑定套接字函数bind()将套接字与需要进行网络通信的地址信息建立连接。服务器端必须进行bind()操作,但客户端可以不手动进行bind(),等到通信连接后客户端可以自动进行bind()操作。
3、设置监听模式
使用listen()将套接字标记为被动模式,即作为accept()请求连接接入的套接字。
listen()函数的用法:
函数listen()
所需头文件:#include<sys/types.h>
#include<sys/socket.h>
函数原型:int listen(int sockfd, int backlog)
函数参数:
sockfd 设定监听的套接字的文件描述符,该套接字必须为SOCK_STREAM类型或SOCK_SEQPACKET类型
backlog 请求队列的最大请求数。若该队列已满,则客户端会收到ECONNREFUSED错误,以便通知客户端在后续重试连接。该值默认为5。
函数返回值:
成功:0
失败:-1
完成监听后,服务器会暂时阻塞,等待客户端发来连接请求(由客户端的connect()函数发送),并使用accept()响应该请求。
4、响应连接请求
服务器端使用accept()函数响应客户端的连接请求。
accept()函数的用法:
函数accept()
所需头文件:#include<sys/types.h>
#include<sys/socket.h>
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
函数参数:
sockfd 套接字文件描述符,该套接字必须由socket()创建,bind()绑定,listen()设置监听
addr 保存客户端的地址信息,结构体类型见bind()第二个参数。
必须使用地址传递的方式传参,或设定为NULL。若设定为NULL则表示不保存客户端地址信息,并且第三个参数addrlen也必须为NULL
addrlen 保存客户端addr结构体的长度,单位为字节,必须使用地址传递的方式传参。若第二个参数addr设定为NULL则该参数也必须为NULL
函数返回值:
成功:建立好连接的套接字文件描述符
失败:-1
5、接收数据与发送数据
在使用socket()、bind()、listen()和accept()后,服务器端与客户端已经成功的建立了通信,此时可以使用send()向对方发送数据,使用recv()接收对方发送的数据。
//除了send()/recv(),有时也可以使用read()/write()来接收和发送数据。其中write()可以取代send(),read()可以取代recv()。使用send()/recv()还是read()/write()全凭程序员个人习惯,但是通篇代码使用要统一。
send()函数的用法:
函数send()
所需头文件:#include<sys/types.h>
#include<sys/socket.h>
函数原型:int send(int sockfd, const void *buf, size_t len, int flags)
函数参数:
sockfd 需要发送数据的套接字文件描述符
buf 发送数据的缓冲区首地址
len 发送数据的缓冲区长度
flags 通常设定为0。若flags设定为0,则send()与write()等价。
函数返回值:
成功:实际发送的字节数
失败:-1
recv()函数的用法:
函数recv()
所需头文件:#include<sys/types.h>
#include<sys/socket.h>
函数原型:int recv(int sockfd, void *buf, size_t len, int flags)
函数参数:
sockfd 需要接收数据的套接字文件描述符
buf 接收数据的缓冲区首地址
len 接收数据的缓冲区长度
flags 通常设定为0。若flags设定为0,则recv()与read()等价。
函数返回值:
成功:实际接收的字节数
失败:-1
若调用recv()后暂时未收到对方send()的数据,则recv()会阻塞等待直至数据到达,如果发送方关闭recv()会立刻返回0。
6、关闭套接字
若通信双方通信完毕,则需要调用close()关闭双方通信。
函数close()的用法:
函数close()
所需头文件:#include<unistd.h>
函数原型:int close(int sockfd);
函数参数:
sockfd 套接字文件描述符
函数返回值:
成功:0
失败:-1
使用TCP通信时连接是双向的(同时可读写),当我们使用close()时会将读写通道都关闭。有时我们只希望关闭一个通道(只读/只写),此时可以使用shutdown()来关闭通道。
函数shutdown()的用法:
函数shutdown()
所需头文件:#include<sys/socket.h>
函数原型:int shutdown(int sockfd, int how);
函数参数:
sockfd 套接字文件描述符
how 关闭套接字的指定方向,具体取值如下:
SHUT_RD 0 关闭读,之后无法再继续接收数据
SHUT_WR 1 关闭写,之后无法再继续发送数据
SHUT_RDWR 2 同时关闭读写,此时同close()
函数返回值:
成功:0
失败:-1
二、TCP编程——客户端
使用TCP通信的客户端的编程流程如下:
与服务器端类似,如果需要使用TCP协议创建一个访问TCP服务器的客户机,需要以下步骤:
-创建套接字
-绑定套接字(可选)
-向服务器发送连接请求
-接收/发送数据
-断开连接
//上文出现过的函数不再讲解
1、向服务器发送连接请求
客户端使用connect()向TCP服务器发送连接请求,服务器端使用accept()接收该请求
函数connect()的用法:
函数connect()
所需头文件:#include<sys/types.h>
#include<sys/socket.h>
函数原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
函数参数:
sockfd 需要发送数据的套接字文件描述符
addr 发送数据的结构体,必须使用地址传递传参。结构体定义见bind()
addrlen 保存addr结构体的长度
函数返回值:
成功:0
失败:-1
以上就是TCP编程需要的所有系统调用函数。除了这些之外,还需要其他的一些函数。
2、IP地址转换函数
通常情况下,我们输入的IP地址为字符串,但是网络连接时需要具体的数值。因此需要使用IP地址转换函数将字符串的IP地址转换成数值类型的IP地址。
IP地址转换函数有3个:inet_addr()、inet_pton()、inet_ntop()。其中inet_addr()只能用于IPv4协议,而inet_pton()和inet_ntop()既可以用于IPv4协议,又可以用于IPv6协议。由于本章以IPv4为主,因此只使用inet_addr()。
inet_addr()与inet_pton()用于将字符串类型的IP地址转换成数字类型的IP地址,而inet_ntop()则是将数字类型的IP地址转换成字符串类型的IP地址
函数inet_addr()、inet_pton()和inet_ntop()的用法:
函数inet_addr()
所需头文件:#include<arpa/inet.h>
函数原型:int inet_addr(const char *cp);
函数参数:
cp 需要转换的IP地址的字符串(IPv4,点分十进制数)
函数返回值:
成功:32位二进制IP地址
失败:-1
注意:inet_addr()只能用于IPv4协议
函数inet_pton()
所需头文件:#include<arpa/inet.h>
函数原型:int inet_pton(int af, const char *src, void *dst);
函数参数:
af 需要转换的IP地址的协议,有两个取值:
AF_INET IPv4通信协议
AF_INET6 IPv6通信协议
src 需要转换的IP地址的字符串
dst 转换后的数据存储位置的首地址
函数返回值:
成功:0
失败:-1
函数inet_ntop()
所需头文件:#include<arpa/inet.h>
函数原型:const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
函数参数:
af 需要转换的IP地址的协议,有两个取值:
AF_INET IPv4通信协议
AF_INET6 IPv6通信协议
src 需要转换的二进制IP地址
dst 转换后的字符串存储位置的首地址,必须不能为NULL
size dst的可用大小
函数返回值:
成功:dst地址(非空)
失败:NULL
3、字节序转换函数
由于现代的计算机中有两种字节序——大端序和小端序,且两种字节序存储数据的方式完全不同,因此需要字节序转换。
大端序通常用于网络通信,而小端序通常用于操作系统内部。因此每次在发送数据前和接收数据后都需要使用字节序转换函数将数据进行转换。
字节序转换函数有4个:htons()、ntohs()、htonl()、ntohl()。其中:
h 代表host,即本地
n 代表network,即网络
s 代表short,即短数据,通常用于端口号转换
l 代表long,即长数据,通常用于IP地址转换
函数htons()、ntohs()、htonl()、ntohl()的用法:
函数htons()、ntohs()、htonl()、ntohl()
所需头文件:#include<arpa/inet.h>
函数原型:
uint16_t htons(uint16_t hostshort);
uint16_t ntohs(uint16_t netshort);
uint32_t htonl(uint32_t hostlong);
uint32_t ntohl(uint32_t netlong);
函数参数:
hostshort 主机字节序数据(16位无符号)
netshort 网络字节序数据(16位无符号)
hostlong 主机字节序数据(32位无符号)
netlong 网络字节序数据(32位无符号)
函数返回值:
成功:转换后的数据
失败:-1
示例:使用TCP进行通信,代码分成服务器端和客户端两部分
//服务器端
//文件server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#define BUFFER 128
int main(int argc, const char *argv[])
{
int listenfd,connfd;//通信用套接字,其中listenfd用于服务器本身创建连接用套接字,connfd用于accept()函数返回用套接字
struct sockaddr_in servaddr,cliaddr;//保存地址与端口号结构体,其中servaddr用于服务器信息,cliaddr用于accept()传参保存客户机信息
//由于使用IPv4方式,所以直接定义sockaddr_in类型结构体,在传参时直接进行强制类型转换即可
socklen_t peerlen;//结构体大小
char buf[BUFFER];//保存信息缓冲区
if(argc<3)
{
printf("too few argument\n");
printf("Usage: %s <ip> <port>\n",argv[1]);
exit(0);
}
/*1.创建套接字socket()*/
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
exit(0);
}
printf("listenfd is %d\n",listenfd);//创建成功
/*1.5.设定servaddr成员,准备绑定bind()*/
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;//协议:IPv4
servaddr.sin_port = htons(atoi(argv[2]));//端口号,由argv[2]给定
servaddr.sin_addr.s_addr = inet_addr(argv[1]);//IP地址:由argv[1]给定
/*2.绑定套接字bind()*/
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)//注意强制类型转换与地址传递
{
perror("bind");
exit(0);
}
printf("bind success!\n");//绑定成功
/*3.设定监听listen()*/
if(listen(listenfd,5)<0)
{
perror("listen");
exit(0);
}
printf("Listening...\n");//监听成功
peerlen = sizeof(cliaddr);
while(1)//使用循环,让服务器不会立即退出
{
/*4.使用accept()接收客户机连接请求*/
if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&peerlen))<0)
{
perror("accept");
exit(0);
}
memset(buf,0,sizeof(buf));
/*5.使用recv()接收客户机信息*/
if(recv(connfd,buf,BUFFER,0)<0)
{
perror("recv");
exit(0);
}
printf("Received a message:%s\n",buf);//接收成功并打印
strcpy(buf,"Welcome to server");
/*6.使用send()向客户机发送信息*/
send(connfd,buf,BUFFER,0);
/*7.关闭连接*/
close(connfd);
}
close(listenfd);
return 0;
}
//客户端
//文件client.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#define BUFFER 128
int main(int argc, const char *argv[])
{
int sockfd;//通信用套接字
char buf[BUFFER]="Hello World";//保存信息缓冲区
struct sockaddr_in myaddr;//保存地址与端口号结构体
if(argc<3)
{
printf("too few argument\n");
printf("Usage: %s <ip> <port>\n",argv[0]);
exit(0);
}
/*1.创建套接字socket()*/
if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
exit(0);
}
/*1.5.设定myaddr成员*/
bzero(&myaddr,sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(atoi(argv[2]));
myaddr.sin_addr.s_addr = inet_addr(argv[1]);
/*2.使用connect()与服务器进行连接*/
if(connect(sockfd,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)//若未开启服务器,则这步会报错
{
perror("connect");
exit(0);
}
/*3.使用send()发送信息*/
send(sockfd,buf,sizeof(buf),0);
/*4.使用recv()接收服务器信息*/
if(recv(sockfd,buf,sizeof(buf),0)<0)
{
perror("recv");
exit(0);
}
printf("recv from server: %s\n",buf);
/*5.关闭连接*/
close(sockfd);
return 0;
}
练习1:使用TCP协议连接服务器端与客户端,客户端可以不断向服务器端传输信息,直至输入特定信息(例如"byebye")服务器才会与客户端断开连接
答案:
//文件server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#define BUFFER 128
int main(int argc, const char *argv[])
{
int listenfd,connfd;
struct sockaddr_in servaddr,cliaddr;
socklen_t peerlen;
char buf[BUFFER];
if(argc<3)
{
printf("too few argument\n");
printf("Usage: %s <ip> <port>\n",argv[1]);
exit(0);
}
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
exit(0);
}
printf("listenfd is %d\n",listenfd);
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]);
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
{
perror("bind");
exit(0);
}
printf("bind success!\n");
if(listen(listenfd,5)<0)
{
perror("listen");
exit(0);
}
printf("Listening...\n");
peerlen = sizeof(cliaddr);
if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&peerlen))<0)
{
perror("accept");
exit(0);
}
printf("Connect:%d\n",connfd);
while(1)
{
memset(buf,0,sizeof(buf));
if(recv(connfd,buf,BUFFER,0)<0)
{
perror("recv");
exit(0);
}
printf("Received a message:%s",buf);
//printf("[%s:%d] %s",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port),buf);
if(strncmp(buf,"byebye",6)==0)//指定断开连接信息为"byebye"
{
printf("Disconnect:%d\n",connfd);
close(connfd);
break;
}
}
close(listenfd);
return 0;
}
//文件client.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#define BUFFER 128
int main(int argc, const char *argv[])
{
int sockfd;
char buf[BUFFER];
struct sockaddr_in myaddr;
if(argc<3)
{
printf("too few argument\n");
printf("Usage: %s <ip> <port>\n",argv[0]);
exit(0);
}
if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
exit(0);
}
bzero(&myaddr,sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(atoi(argv[2]));
myaddr.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sockfd,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)
{
perror("connect");
exit(0);
}
while(1)
{
memset(buf,0,sizeof(buf));
fgets(buf,BUFFER,stdin);
send(sockfd,buf,sizeof(buf),0);
if(strncmp(buf,"byebye",6)==0)//指定断开连接信息为"byebye"
{
printf("Client will exit\n");
close(sockfd);
break;
}
}
return 0;
}
运行这个练习代码时,可以尝试不打开服务器端,直接运行数据端发送数据。可以发现客户端是无法启动的(Connection refused访问被拒绝)。
这是因为TCP协议是可靠连接,在通信之前必须在服务器端与客户端建立可靠连接。
//练习2:练习文件IO+网络编程
练习2:在服务器端有一个存放成绩的文件score.txt
学号 姓名 成绩
10101 Liu 98
10102 Zhao 97
10103 Sun 92
10104 Chen 97
10105 Tian 95
10106 Li 99
10107 Xiao 96
10108 Meng 94
10109 Lion 91
10110 Bunk 97
客户端向服务器端发送学号,服务器端查找文件内容并返回结果。若有该学号信息,则返回成绩;若无该学号信息,则返回#代表查找失败
答案:
//文件server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#define BUFFER 128
int main(int argc, const char *argv[])
{
int listenfd,connfd;
FILE *scorefd;
struct sockaddr_in servaddr,cliaddr;
socklen_t peerlen;
int flag = 0;
char buf[BUFFER],recvbuf[BUFFER]={0};
if(argc<3)
{
printf("too few argument\n");
printf("Usage: %s <ip> <port>\n",argv[1]);
exit(0);
}
if((scorefd=fopen("score.txt","r+"))==NULL)
{
perror("cannot open score.txt");
exit(0);
}
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
exit(0);
}
printf("listenfd is %d\n",listenfd);
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]);
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
{
perror("bind");
exit(0);
}
printf("bind success!\n");
if(listen(listenfd,5)<0)
{
perror("listen");
exit(0);
}
printf("Listening...\n");
peerlen = sizeof(cliaddr);
if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&peerlen))<0)
{
perror("accept");
exit(0);
}
printf("Connect:%d\n",connfd);
memset(buf,0,sizeof(buf));
if(recv(connfd,recvbuf,BUFFER,0)<0)
{
perror("recv");
exit(0);
}
printf("Received a number:%s",recvbuf);
while(fgets(buf,BUFFER,scorefd)!=NULL)//查询文件内容
{
if(strncmp(buf,recvbuf,5)==0)//如果发现了目标学号循环停止
{
flag = 1;
break;
}
}
if(flag==1)//找到
{
send(connfd,buf,sizeof(buf),0);//发送给客户端
}
else//未找到
{
memset(buf,0,sizeof(buf));
sprintf(buf,"#");
send(connfd,buf,sizeof(buf),0);//给客户端发送#表示查找失败
}
close(connfd);
close(listenfd);
fclose(scorefd);
return 0;
}
//文件client.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#define BUFFER 128
int main(int argc, const char *argv[])
{
int sockfd;
char buf[BUFFER];
struct sockaddr_in myaddr;
if(argc<3)
{
printf("too few argument\n");
printf("Usage: %s <ip> <port>\n",argv[0]);
exit(0);
}
if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
exit(0);
}
bzero(&myaddr,sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(atoi(argv[2]));
myaddr.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sockfd,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)
{
perror("connect");
exit(0);
}
memset(buf,0,sizeof(buf));
printf("Please input number:");
fgets(buf,BUFFER,stdin);
send(sockfd,buf,sizeof(buf),0);
memset(buf,0,sizeof(buf));
if(recv(sockfd,buf,sizeof(buf),0)<0)
{
perror("recv");
exit(0);
}
if(strncmp("#",buf,1)!=0)
{
printf("recv from server:\n%s",buf);
}
else
{
printf("No result!\n");
}
return 0;
}
//练习3:练习线程编程+网络编程
练习3:使用多线程编程,实现服务器端与客户端的双向通信,即服务器端与客户端可以同时接收和发送数据。当客户端输入"byebye"时服务器端与客户端断开连接
//注意:本题使用进程编程需要进程通信手段,代码难度大幅度上升,因此不推荐使用进程编程
//服务器端
//文件server_thread.c
//注意:由于服务器接收到"byebye"后需要与客户端断开连接,因此在主进程内接收数据而在线程内发送数据
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<arpa/inet.h>
#define BUFFER 128
void *thread_send(void *arg)
{
int newsockfd = *((int*)arg);
char sendbuffer[BUFFER]={0};
while(1)
{
fgets(sendbuffer,BUFFER,stdin);
send(newsockfd,sendbuffer,BUFFER,0);
}
pthread_exit(NULL);//可以省略
}
int main(int argc, const char *argv[])
{
int listenfd,connfd;
struct sockaddr_in servaddr,cliaddr;
socklen_t peerlen;
char buf[BUFFER];
pthread_t sendtid;
if(argc<3)
{
printf("too few argument\n");
printf("Usage: %s <ip> <port>\n",argv[1]);
exit(0);
}
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
exit(0);
}
printf("listenfd is %d\n",listenfd);
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]);
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
{
perror("bind");
exit(0);
}
printf("bind success!\n");
if(listen(listenfd,10)<0)
{
perror("listen");
exit(0);
}
printf("Listening...\n");
peerlen = sizeof(cliaddr);
if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&peerlen))<0)
{
perror("accept");
exit(0);
}
printf("Connect:[%s:%d]\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
if((pthread_create(&sendtid,NULL,thread_send,&connfd))!=0)
{
perror("create thread");
exit(0);
}
while(1)
{
if((recv(connfd,buf,BUFFER,0))<0)
{
perror("recv");
exit(0);
}
printf("Received:%s",buf);
if(strncmp(buf,"byebye",6)==0)
{
break;
}
}
close(connfd);
close(listenfd);
return 0;
}
//客户端
//文件client_thread.c
//注意:由于客户端发送"byebye"后与服务器端断开连接,因此在主进程内发送数据而在线程内接收数据
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#define BUFFERSIZE 128
void *thread_recv(void *arg)
{
int listenfd=*((int*)arg);
char recvbuf[BUFFERSIZE]={0};
while(1)
{
if(recv(listenfd,recvbuf,BUFFERSIZE,0)<0)
{
perror("recv");
exit(0);
}
printf("recv:%s",recvbuf);
bzero(recvbuf,BUFFERSIZE);
}
pthread_exit(NULL);//可以省略
}
int main()
{
int sockfd;
pthread_t pthrecv;
char buf[BUFFERSIZE]={0};
struct sockaddr_in servaddr;
if(argc<3)
{
printf("too few argument\n");
printf("Usage: %s <ip> <port>\n",argv[1]);
exit(0);
}
//创建socket
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("socket");
exit(0);
}
//设置sockaddr_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]);
//调用connect()函数向服务器端建立TCP链接
if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
{
perror("connect");
exit(0);
}
//创建线程,用于接收信息
if((pthread_create(&pthrecv,NULL,thread_recv,&sockfd))!=0)
{
perror("创建线程");
exit(0);
}
//发送消息给服务器端
while(1)
{
fgets(buf,BUFFERSIZE,stdin);
send(sockfd,buf,BUFFERSIZE,0);
if(strncmp(buf,"byebye",6)==0)
break;
}
close(sockfd);
return 0;
}