一、建立套接字socket
1.套接字定义
套接字,英文名字叫做socket,是一个指向传输提供者的句柄(句柄:一种特殊的智能指针,每一个具体对象的ID,根据这个ID,操作系统可以将消息发给指定的对象),在Linux系统的网络编程中通过操作该句柄来实现网络通信和管理的。
2. 套接字分类
套接字分为三大类,分别为原始套接字(SOCK_RAW)、流式套接字(SOCKET_STREAM),流式套接字(SOCK_DGRAM)
(1)原始套接字:能够使程序开发人员对底层的网络传输机制进行控制,在原始套接字下接收的数据含有IP头。
(2)流式套接字:提供了双向、有序、可靠的数据传输,该类套接字在通信前需要双方建立连接,TCP协议采用的就是该套接字。
(3)数据包套接字:提供双向的数据流,但是它不能保证数据传输的可靠性、有序性和无重复性,UDP协议采用的就是该套接字。
这三种套接字分类在套接字创建函数中有使用到,例如:
① 如果创建的是TCP/IP协议,则应使用SOCKET_STREAM,sockfd =socket(AF_INET, SOCK_STREAM, 0);
② 如果创建的是UDP协议,则应使用SOCK_DGRAM,sockfd =socket(AF_INET, SOCK_DGRAM, 0);
3.套接字相关结构体
(1)struct sockaddr
#include <netinet/in.h>
struct sockaddr
{
unsigned short sa_family; /*地址族,2字节,AF_xxx*/
char sa_data[14]; /*14字节的协议地址*/
}
(2)struct sockaddr_in
#include <netinet/in.h>
struct sockaddr_in
{
short int sin_famliy; /*地址族,2字节*/
unsigned short sin_port; /*端口号,2字节,该参数填0时,表示系统随机选择一个未被使用的端口号*/
struct in_addr sin_addr; /*IP地址,32位,4字节,该字节填写INADDR_ANY,表示填入本机地址*/
unsigned char sin_zero[8]; /*填充8字节0,以保持与struct sockaddr 同样大小*/
}
注意:
① sin_port所填写的端口号要设置为大于1024的数值,因为0~1024是保留端口号。
② 在使用该结构体时候,sin_port、sin_addr不能直接填入数值,需要将其转化为网络字节优先顺序。
(3)网络字节优先顺序
互联网上,数据以高位字节优先顺序在网络上传输,所以对于在内部是以低字节优先方式存储数据的机器,在互联网上传输数据时候,要进行转换,否则就会出现数据不一致的现象。
在socket网络编程中,有两个基本是术语是必须掌握的,网络字节顺序(NBO,Network Byte Order)和主机字节顺序(HBO,Host Network Order)。
网络字节顺序,NBO是网络数据在传输中的规定的数据格式,从高到低位顺序存储,即低字节存储在高地址,高字节存储在低地址;即“大端模式”。网络字节顺序可以避免不同主机字节顺序的差异。
主机字节顺序,HBO则与机器CPU相关,数据的存储顺序由CPU决定。
下面是在Linux系统下的几个字节顺序的转换函数
#include <arpa/inet.h>
uint32_t htonl(unit32_t hostlong); //htonl()函数把32位值从主机字节顺序转化为网络字节顺序
uint16_t htons(uint16_t hostshort); //htons把16位值从主机字节顺序转化为网络字节顺序
uint32_t ntohl(uint32_t netlong); //把32位值从网络顺序转化为主机字节顺序
uint16_t ntohs(uint16 netshort); //把16位值从网络字节顺序转化成主机字节顺序
in_addr_t inet_addr(“xxx.xxx.xxx.xxx”); //将字符串式的IP地址转换为32位网络字节序的整数形式
char *inet_ntoa(struct in_addr in); //将一个32位网络字节序的二进制IP地址转换成相应的点分十进制的IP地址字符串
(4)struct sockaddr_in的使用方法
#include <arpa/inet.h>
#include <netinet/in.h>
Local_addr.sin_family = AF_INET;
Local_addr.sin_addr.s_addr = htonl(INADDR_ANY); //填写本机IP地址
/*注意:如果需要填写别的IP地址,例如192.168.2.88,则需要用到inet_addr,将字符串转化为32位网络字节的整形数值*/
/*Local_addr.sin_addr.s_addr = inet_addr(“192.168.2.88”)*/
Local_addr.sin_port = htons(50001);
bzero(&(Local_addr.sin_zero),8);
(5)struct sockaddr与struct sockaddr_in的区别与联系
共同点:
① 空间大小相同,都是16字节
② 都有family属性
③ sockaddr和sockaddr_in包含的数据都是一样的
不同点:
① sockaddr用除family外的14个字节来表示sa_data,而sockaddr_in把14个字节拆分成sin_port, sin_addr和sin_zero。分别表示端口、ip地址。sin_zero用来填充字节使sockaddr_in和sockaddr保持一样大小。
② 程序员不应操作sockaddr,sockaddr是给操作系统用的程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。
具体使用方法:程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给调用函数。
举例:
int sockfd;
struct sockaddr_in servaddr;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
/* 填充struct sockaddr_in */
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
/* 强制转换成struct sockaddr */
connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
4.套接字创建函数socket
无论是TCP还是UDP通信,无论是作为客户端还是服务器端,建立通信的第一步都是先创建套接字。
(1)函数作用:为了创建套接字,该函数返回一个类似于文件描述符的句柄。
(2)头文件:
#include <sys/types>
#include <sys/socket.h>
(3)函数原型:int socket(int domain,int type,int protocol)
(4)函数参数:
① domain,代表所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
② type,指定套接字的类型:
SOCKET_STREAM(流式套接字)
SOCK_DGRAM(数据包套接字)
SOCK_RAW(原始套接字)
③ protocol,常用的有 IPPROTO_TCP 、IPPTOTO_UDP、0,分别表示 TCP 传输协议、UDP 传输协议、自动推演。
(5)protocol:一般情况下有了 domain和 type 两个参数就可以创建套接字了,填写0,操作系统会自动推演出协议类型编程中也大多使用填写0的方法。除非遇到这样的情况:有两种不同的协议支持同一种地址类型和数据传输类型。如果我们不指明使用哪种协议,操作系统是没办法自动推演的。
① 参数 domain的值为AF_INET,type选择 SOCK_STREAM ,那么满足这两个条件的协议只有 TCP,所以下面两种写法是等价的:
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //IPPROTO_TCP表示TCP协议
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //0表示自动推演所对应的协议
② 参数 domain的值为AF_INET,type选择 SOCK_DGRAM,那么满足这两个条件的协议只有 UDP,所以下面两种写法是等价的:
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //IPPROTO_UDP表示UDP协议
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //0表示自动推演所对应的协议
(6)返回值:一个人int型的套接字描述符,创建城管后,这个值一般大于2,后面对套接字进行操作的函数都会调用它。-1:创建失败,返回值为0,1,2的基本属于标准输入输出套接字标识。通常用户自己创建的socket不会出现这个问题。
套接字描述符:是一个指向内部数据结构的指针,它指向描述符表入口,调用socket函数时,套接字执行体将创建一个套接字,也就是为一个套接字数据结构分配存储空间。
5.perror函数
perror函数使:perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。
参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和现在的errno所对应的错误一起输出。
perror函数使用实例:
#include<stdio.h>
int main(void)
{
FILE *fp;
fp=fopen("/root/noexitfile","r+");
if(NULL==fp)
{
perror("/root/noexitfile");
}
return 0;
}
运行结果:
二、绑定套接字bind
特别注意,bind函数是TCP服务器端才使用的,客户端无需绑定套接字。通过socket()函数调用返回一个套接字描述符后,在接收客户端连接请求以前,必须先绑定套接字,也就是将套接字与本机的IP地址和端口相关联,随后就可以在该端口下进行监听服务。
(1)头文件:
#include <sys/types.h>
#include <sys/socket.h>
(2)函数作用:把一个本地IP地址与端口号赋予一个套接字
(3)函数原型:int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen)
(4)函数参数:
① sockfd,调用socket()函数返回的套接字描述符
② my_addr,一个指向包含本机IP地址及端口号的sockaddr指针,通常将sockaddr_in类型强转为sockaddr类型使用
③ addrlen,socklen_t和int差不多,4字节,该参数表示struct sockaddr的长度,即sizeof(struct sockaddr);
(5)返回值:绑定成功返回0;出现错误返回-1,并将errno设置成相应的错误号,即可用perror函数打印出错误信息。
三、建立连接connect函数(客户端使用)
面向连接的客户程序使用connect函数来配置套接字并与远端服务器建立一个TCP连接。言外之意,客户端才使用connect函数。
connect函数是一个阻塞函数,通过TCP三次握手服务器端建立连接。客户端主动连接服务器,建立连接方式通过TCP三次握手,通知Linux内核自动完成TCP三次握手连接。如果连接成功为0 失败返回值-1。一般的情况下,客户端的connect函数默认是阻塞行为,直到三次握手阶段成功为止。
于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手连接。
(1)头文件:
#include <sys/types.h>
#include <sys/socket.h>
(2)函数作用:用于建立与指定socket的连接,连接服务器端(通知Linux内核,让内核自动完成TCP三次握手)。
(3)函数原型:int connect(int sockfd, const struct sockaddr * serv_addr, int serv_addrlen);
(4)函数参数:
① sockfd,调用socket()函数返回的套接字描述符
② serv_addr,包含远程服务器IP地址和端口号的指针
③ serv_addr,包含远程服务器IP地址和端口号的指针的长度,即sizeof(struct sockaddr)
(5)返回值: 0:成功,-1:失败,并将错误码保存至errno中,可用perror函数打印。
四、监听模式listen函数
该函数使套接字处于被动监听模式,并为该套接字建立一个输入数据队列,将达到的服务请求保存在该队列中,直到程序对他们处理。
对于服务器,它是被动连接的。举一个生活中的例子,通常的情况下,移动的客服(相当于服务器)是等待着客户(相当于客户端)电话的到来。而这个过程,需要调用listen()函数。
如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
(1)头文件:
#include <sys/types.h>
#include <sys/socket.h>
(2)函数作用:把进程变为一个服务器,并指定相应的套接字变为被动连接。
(3)函数原型:int listen(int sockfd, int backlog);
(4)函数参数:
① sockfd,调用socket()函数返回的套接字描述符
② backlog,告诉内核连接队列的长度。详细介绍参考https://blog.csdn.net/kongxian2007/article/details/49153801
(5)返回值: 0:成功,-1:失败,并将错误码保存至errno中,可用perror函数打印。
注意:listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后listen()函数就结束。这样的话,当有一个客户端主动连接(connect()),Linux 内核就自动完成TCP 三次握手,将建立好的链接自动存储到队列中,如此重复。所以,只要 TCP 服务器调用了 listen(),客户端就可以通过 connect() 和服务器建立连接,而这个连接的过程是由内核完成。
示例:
服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
unsigned short port = 8000;
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
if( err_log != 0)
{
perror("binding");
close(sockfd);
exit(-1);
}
err_log = listen(sockfd, 10);
if(err_log != 0)
{
perror("listen");
close(sockfd);
exit(-1);
}
printf("listen client @port=%d...\n",port);
sleep(10); // 延时10s
system("netstat -an | grep 8000"); // 查看连接状态
return 0;
}
客户端
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char *argv[])
{
unsigned short port = 8000; // 服务器的端口号
char *server_ip = "10.221.20.12"; // 服务器ip地址
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr)); // 初始化服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 主动连接服务器
if(err_log != 0)
{
perror("connect");
close(sockfd);
exit(-1);
}
system("netstat -an | grep 8000"); // 查看连接状
while(1);
return 0;
}
运行程序时,要先运行服务器,再运行客户端,运行结果如下:
五、接收请求accept函数(服务器端使用)
accept函数让服务器接收客户端的连接请求。在建立好连接队伍后,服务器就调用accept函数,然后睡眠并等待客户端的连接请求。
accept() 函数会发生阻塞: 从处于established 状态的队列(即已经完成三次握手后)中取出完成的连接,当队列中没有完成连接时候会形成阻塞,直到取出队列中已完成连接的用户连接为止。
(1)头文件:
#include <sys/types.h>
#include <sys/socket.h>
(2)函数作用:用于接收客户端的连接请求。
(3)函数原型:int accpt(int sockfd, const struct sockaddr * client_addr, int client_addrlen);
(4)函数参数:
① sockfd,调用socket()函数返回的套接字描述符
② client_addr,包含远程客户端IP地址和端口号的指针
③ client_addr,包含远程客户端IP地址和端口号的指针的长度,即sizeof(struct sockaddr)
(5)返回值:
① -1:失败,并将错误码保存至errno中,可用perror函数打印。
② 非负数:返回值是一个新的套接字描述符,它表示和客户端的新的连接,可以把它理解成是一个客户端的socket
示例:
int acceptedfd;
unsigned char buf[256];
acceptedfd = accept(socketfd,(struct sockaddr *)&client_address, (int *)&fromlen); //接收客户端连接请求
recv(acceptedfd ,buf,256,0); //接收客户端发送的数据
六、数据传输(send、recv、close)
- size_t在不同平台上,其具有不同的定义:
- /* sparc 64 bit */
- typedef unsigned long __kernel_size_t;
- typedef long __kernel_ssize_t;
- /* sparc 32 bit */
- typedef unsigned int __kernel_size_t;
- typedef int __kernel_ssize_t;
1.send()函数
send函数,向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR。
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
(1)头文件:
#include <sys/types.h>
#include <sys/socket.h>
(2)函数作用:向一个已经连接的socket发送数据。
(3)函数原型:ssize_t(int sockfd, const void *buf, size_t len, int flag);
(4)函数参数:
① sockfd,调用socket()函数返回的套接字描述符
② const void *buf,一个指向所要发送的数据的指针
③ len,是以字节为单位的数据的长度
④ flag, 0: 与write()无异,一般情况下填0,特殊情况下才填写下面三种情况
MSG_DONTROUTE:告诉内核,目标主机在本地网络,不用查路由表
MSG_DONTWAIT:将单个I/O操作设置为非阻塞模式
MSG_OOB:指明发送的是带外信息
(5)返回值:
① -1:失败,并将错误码保存至errno中,可用perror函数打印。
② 大于0,表示实际发送的字节数
注意:send函数返回实际上发送出的字节数,可能会少于用户希望发送的数据。在程序中,应该将send函数的返回值与想要发送的字节数(len)进行比较,当send函数的返回值和len长度不一致时候,应该对这种情况进行处理。
2.recv()函数
不论是客户还是服务器应用程序都用recv函数来接收来自TCP连接的另一端所发送过来的数据。
(1)头文件:
#include <sys/types.h>
#include <sys/socket.h>
(2)函数作用:接收TCP另一端发送过来的数据。
(3)函数原型:ssize_t recv(int sockfd, const void *buff, size_t nbytes, int flags);
(4)函数参数:
① sockfd,调用socket()函数返回的套接字描述符
② const void *buf,一个指向接收数据缓冲区的指针
③ len,缓冲区的长度,缓冲区的长度一般定义的较大,防止接收的数据过大,遗漏数据。
④ flag,
0:与read()相同,区别在于read函数读取缓冲区的数据之后,会将缓冲区的数据删除,而recv不会
MSG_DONTWAIT:将单个I/O操作设置为非阻塞模式
MSG_OOB:指明发送的是带外信息
MSG_PEEK:可以查看可读的信息,在接收数据后不会将这些数据丢失
MSG_WAITALL:通知内核直到读到请求的数据字节数时,才返回。
(5)返回值:
① -1:失败,并将错误码保存至errno中,可用perror函数打印。
② 大于0,表示实际接收的字节数
注意:recv接收分为两种情况,一种是客户端,一种是服务器端,示例:
/*客户端*/
int rr_1;
if ( (socketfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) //创建套接字
{
perror("Error to build socket\n");
return ;
}
rr_1 = connect(socketfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr)); //连接服务器端
if ( rr_1 == -1 )
{
perror("Error connecting to socket\n");
return ;
}
RecvLen = recv(socketfd, RecvBuf,MAXDATASIZE,0); //接收数据
/*服务器端*/
socketfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
bind(socketfd, (struct sockaddr *)&serv_addr, address_len); //绑定套接字
acceptedfd = accept(socketfd,(struct sockaddr *)&client_address, (int *)&fromlen); //接收客户端的连接请求
RecvLen=recv(acceptedfd, RecvBuf,MAXDATASIZE,0); //接收客户端发送过来的数据
3.close()函数
数据操作结束后,就可以调用close()函数来释放该套接字,从而停止在该套接字上的任何数据操作。
(1)头文件:#include <unistd.h>
(2)函数原型:int close(int sockfd);
(3)函数参数: sockfd,所要释放的套接字描述符
4.shutdown()函数
该函数也是关闭套接字的,但是和close不同的是,该函数允许只停止在某个方向上的数据传输,而另一个方向上的数据传输继续进行。
(1)头文件:#include <sys/socket.h>
(2)函数原型:int shutdown(int sockfd,int how)
(3)函数参数:
① sockfd,所要关闭的套接字描述符
② how,允许为关闭操作选择以下几种方式:
0:不允许继续接收数据
1:不允许继续发送数据
2:不允许继续发送和接收数据
如果以上几种行为都不允许,可以直接调用close函数。
(4)返回值:0:成功,-1:失败,并设置相应errno值
七、TCP通信流程
1.服务器端
- 创建套接字socket()
- 配置服务器sockaddr_in结构体
- 绑定套接字bind()
- 监听模式,通知Linux内核三次握手listen()
- 等待客户端连接,调用client_fd = accep()
- 接收客户端信息,client_fd=accept(sockfd,NULL,NULL)
- 发送消息给客户端,(send(client_fd,sendbuf,strlen(sendbuf),0)
2.客户端
- 创建套接字socket()
- 填写所要连接的服务器sockaddr_in结构体
- 主动发起对服务端的链接,调用connect()
- 发送消息给服务器,send(sockfd,buf,strlen(buf),0)
- 接收服务器的消息,recv(sockfd,buf,BUFFER_SIZE,0)
八、TCP通信示例
1.服务器端
///server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321
#define BUFFER_SIZE 1024
#define MAX 5
#include <pthread.h>
int main()
{
struct sockaddr_in servaddr;
int sockfd,client_fd;
char buf[BUFFER_SIZE];
char sendbuf[] = "received data";
/*建立socket连接*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
exit(1);
}
printf("socket id=%d\n",sockfd);
/*设置sockaddr_in结构体中相关参数*/
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(PORT);
servaddr.sin_addr.s_addr=INADDR_ANY;
int i=1; /*允许重复使用本地址与套接字进行绑定*/
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i));
/*绑定函数bind()*/
if(bind(sockfd,(struct sockaddr *) &servaddr,sizeof(servaddr))==-1)
{
perror("bind");
exit(1);
}
printf("Bind success!\n");
/*调用listen函数,创建未处理请求的队列*/
if(listen(sockfd,MAX)==-1)
{
perror("listen");
exit(1);
}
printf("Listen...\n");
/*调用accept函数,等待客户端连接*/
if((client_fd=accept(sockfd,NULL,NULL))==-1)
{
perror("accept");
exit(0);
}
while(1)
{
if(recv(client_fd,buf,BUFFER_SIZE,0)==-1)
{
perror("recv");
exit(0);
}
else
{
printf("Received a message:%s\n",buf);
memset(buf,0,sizeof(buf));
if(send(client_fd,sendbuf,strlen(sendbuf),0)==-1)
{
perror("send");
exit(-1);
}
memset(buf,0,sizeof(buf));
}
}
close(sockfd);
exit(0);
}
2.客户端
///client.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<arpa/inet.h>
#define BUFFER_SIZE 100
int main(int argc,char *argv[])
{
int sockfd,client_fd;
int len;
char buf[BUFFER_SIZE];
struct sockaddr_in servaddr;
if(argc<3)
{
printf("USAGE=%s <serv_in> <serv_port>\n",argv[0]);
exit(-1);
}
/*创建socket*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
exit(-1);
}
else
{
printf("socket build success\r\n");
}
/*创建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函数主动发起对服务端的链接*/
if(connect(sockfd,(struct sockaddr *) &servaddr,sizeof(servaddr))==-1)
{
perror("connect");
exit(-1);
}
/*发送消息给服务端*/
while(1)
{
printf("please input string:\r\n");
scanf("%s",buf);
len = sizeof(buf);
printf("len = %d\r\n",len);
buf[len] = 0;
if(send(sockfd,buf,strlen(buf),0)==-1)
{
perror("send");
exit(-1);
}
memset(buf,0,sizeof(buf));
if(recv(sockfd,buf,BUFFER_SIZE,0)==-1)
{
perror("recv");
exit(0);
}
printf("Received a message:%s\n",buf);
memset(buf,0,sizeof(buf));
}
close(sockfd);
exit(0);
}
运行结果:
先运行服务器端,再运行客户端,客户端输入服务器的IP地址(注意,我这里填的10.144.42.88是我虚拟机的IP地址,查看自己IP地址可以使用ifcong命令)和端口号,回车之后,输入想要发送给服务器的字符串,点回车,发送成功之后,服务器端会回复receive data的信息,表示接收成功。