目录
- 一、Socket编程
- 1、TCP/UDP
- (1)TCP/UDP区别
- (2)TCP/UDP服务端和客户端的通信流程
- 2、IP地址和端口号
- (1)IP地址
- (2)端口号
- 二、字节序
- 1、字节系相关概念
- 2、有关字节序转换的函数
- (1)主机字节序转换网络字节序函数
- (2)网络字节序转换主机字节序函数
- 3、地址转换函数
- (1)点分十进制IP地址转换为二进制IP地址函数
- ①函数inet_aton
- ②函数inet_pton
- (2)二进制IP地址转换为点分十进制IP地址函数
- ①函数inet_ntoa
- ②函数inet_ntop
- 三、Socket套接字相关函数
- 1、Socket 编程流程
- (1)TCP通信流程
- (2)UDP通信流程
- 2、Socket 相关函数
- (1)socket()函数(创建套建字)
- (2)bind()函数(绑定套建字)
- (3)listen()函数(监听被绑定的端口)
- (4)accept()函数(接收连接请求)
- (5)connect()函数(发送连接请求)
- (6)send()、recv()函数(TCP发送、接收信息)
- ①函数send()
- ②函数recv()
- (7)sendto()、recvfrom()函数(UDP发送、接收消息)
- ①函数sendto()
- ②函数recvfrom()
- 四、示例
一、Socket编程
在UNIX、Linux系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。
为了表示和区分已经打开的文件,UNIX/Linux会为每个文件分配一个ID,这个文件就是一个整数,被称为文件描述符
例如:
通常用 0 来表示标准输入文件(stdin),它对应的硬件设备就是键盘;
通常用 1 来表示标准输出文件(stdout),它对应的硬件设备就是显示器。
网络连接也是一个文件,它也有文件描述符
我们可以通过 socket()
函数来创建一个网络连接,或者说打开一个网络文件,socket() 的返回值就是文件描述符(注意在windows下的socket返回的叫文件句柄,并不是叫文件描述符)。有了文件描述符,我们就可以使用普通的文件操作函数来传输数据了,例如:
用 read()
读取从远程计算机传来的数据;
用 write()
向远程计算机写入数据。
1、TCP/UDP
OSI七层网络模型中,各个层之间都有相对应的协议,这里主要关注的是传输层的协议:TCP/UDP协议
首先标准套接字分为TCP和UDP协议两种不同type的工作流程,TCP网络编程相对于UDP来说相对复杂,因为TCP是面向连接的服务,其中包括三次握手建立连接的过程,而UDP则是无连接的服务。
(1)TCP/UDP区别
(1)TCP面向连接(如打电话要先拨号建立连接),而UDP是无连接的,即发送数据之前不需要建立连接。
(2)TCP提供可靠的服务,也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;而UDP则是尽最大努力进行交付,即不保证可靠交付。
(3)TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;而UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(这样对实时应用很有用,如,IP电话,实时视频会议等)。
(4)每一条的TCP只能是点到点的,UDP支持一对一,一对多,多对一和多对多的交互通信。
(5)TCP首部开销20字节;UDP首部开销小,只有8个字节。
(6)TCP的逻辑通信的信道是全双工的可靠信道,而UDP则是不可靠信道。
(2)TCP/UDP服务端和客户端的通信流程
TCP和UDP的网络编程模式有两种,一种是服务器模式,另一种是客户端模式,因为TCP是面向连接的服务,所以在socket机制当中,TCP的服务器模式比UDP的服务器模式多了listen,accept函数,TCP客户端比UDP客户端多了connect函数。下面是TCP和UDP网络编程的两种模式流程图:
TCP:
UDP:
2、IP地址和端口号
(1)IP地址
IP地址是网络中主机(电脑)的标识,在网络中主机想要与其他机器通信就必须拥有一个自己的IP地址,IP地址为32位(IPV4)或者128位(IPV6),每一个数据包都必须携带目的地址IP和源IP地址,路由器依靠此信息为数据包选择最优路由(路线)。(IP地址就有点像是几栋楼中的楼的号码)
对于IPv4来说,IP地址是一个4字节32为的整数。通常使用“点分十进制”的字符串来表示IP地址,如192.168.144.2,用点分割的每一个数字表示一个字节,范围是0~255。
(2)端口号
用于区分一台主机中接收到的数据包应该交给那一个进程进行处理(注意TCP和UDP的端口号是相互独立的)。(如果IP地址是相当于一栋楼的楼号的话,那么端口号就相当于是这栋楼里面的房间的房号)
端口号的作用:
一台拥有IP地址的主机可以提供许多服务,比如Web服务,FTP服务,SMTP服务等。
这些服务完全是可以由一个IP地址来实现的,那么,主机是怎么样区分不同的网络服务的呢?显然不能只靠IP地址,因为IP地址与网络服务的关系是一对多的关系。
实际上通过"IP地址+端口号"来区分不同的服务的。
端口提供了一种访问通道,服务器一般都是通过知名的端口来识别的。例如,对于每个TCP/IP的实现来说,FTP服务的端口号是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。
二、字节序
1、字节系相关概念
字节序:是指多字节数据的存储顺序,在设计计算机系统的时候,有两种处理内存中数据的方法:即大端格式、小端格式。
MSB(Most Significant Byte),最高有效字节,是一个数据中权值最大的那一个字节。
LSB(Least Significant Byte),最低有效字节,是一个数据中权值最小的那一个字节。
小端格式(Little-Endian):将低位字节(LSB)数据存储在低地址。
大端格式(Big-Endian):将高位字节(MSB)数据存储在低地址。
注意:网络字节序=大端字节序
例如:整型数 0x1A2B3C4D 它的MSB是 0x1A,LSB是 0x4D,在内存中存储的方式如下:
为何使用字节序?
- 计算机电路先处理低位字节,效率比较高。因为计算就是从低位开始的,所以计算机内部很多都是小端字节序。
- 格式规范是为人类编写的,大端字节序更符合人类习惯。
网络字节序的定义:将收到的第一个字节的数据当做高位来看待,这就要求发送端的发送的第一个字节应该是高位。而在发送端发送数据时,发送的第一个字节是该数字在内存中起始地址对应的字节。可见多字节数值在发送前,在内存中数值应该以大端法存放。
所以,网络协议指定了通讯字节序:大端。只有在多字节数据处理时才需要考虑字节序,运行在同一台计算机上的进程相互通信时,一般不用考虑字节序,异构计算机之间进行通讯时,需要将自己的字节序转换为网络字节序。
2、有关字节序转换的函数
h代表host,n代表net,s代表short(2个字节),l代表long(4个字节),通过下面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取。
(1)主机字节序转换网络字节序函数
函数原型:
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);//将16位主机字节序数据转换成网络字节序数据
uint32_t htonl(uint32_t hostlong);//将32位主机字节序数据转换成网络字节序数据
功能:
htons
将16位主机字节序数据转换成网络字节序数据
htonl
将32位主机字节序数据转换成网络字节序数据
参数介绍:
hostshort
:需要转换的16位主机字节序数据,uint16_t:unsigned short int
hostlong
:需要转换的32位主机字节序数据,uint32_t:32位无符号整型
返回值:
成功,返回网络字节序的值;失败,返回-1
(2)网络字节序转换主机字节序函数
函数原型:
#include <arpa/inet.h>
uint16_t ntohs(uint16_t netshort);//将16位网络字节序数据转换成主机字节序数据
uint32_t ntohl(uint32_t netlong);//将32位网络字节序数据转换成主机字节序数据
函数功能:
ntohs
将16位网络字节序数据转换成主机字节序数据
ntohl
将32位网络字节序数据转换成主机字节序数据
参数介绍:
netshort
:需要转换的16位网络字节序数据,uint16_t:unsigned short int
netlong
:需要转换的32位网络字节序数据,uint32_t:unsigned int
返回值:
成功,返回主机字节序的值;失败,返回-1
3、地址转换函数
点分十进制(Dotted Decimal Notation)全称为点分(点式)十进制表示法,是IPv4的IP地址标识方法。IPv4中用四个字节表示一个IP地址,每个字节按照十进制表示为0~255。
点分十进制就是用4组从0~255的数字,来表示一个IP地址。如192.168.1.1。
(1)点分十进制IP地址转换为二进制IP地址函数
①函数inet_aton
函数原型:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
说明:
将点分十进制字符串转换成32位无符号整数,只适用于IPV4地址
参数介绍:
cp
:将被转换的点分十进制IP地址
inp
:保存被转换成二进制的IP地址
返回值:
如果地址合法,则返回非0值,反之返回0值
②函数inet_pton
函数原型:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
说明:
该函数将字符串src转换为af地址类型协议簇的网络地址,并存储到dst中。
对于af参数,必须为AF_INET
或AF_INET6
,适用于IPV4和IPV6地址
参数介绍:
af
:AF_INET或AF_INET6
src
:将被转换的点分十进制的IP地址
dst
:保存被转换的二进制IP地址
返回值:
转换成功则返回1,对于指定的地址类型协议簇,如果不是一个有效的网络地址,将转换失败,返回 0,如果指定的地址类型协议簇不合法,将返回-1,并且errno设置为EAFNOSUPPORT
(2)二进制IP地址转换为点分十进制IP地址函数
①函数inet_ntoa
函数原型:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
说明:
inet_ntoa()
用来将参数in所指的大端网络字节序二进制的数字转换成ipv4点分十进制字符串网络地址,然后将指向此网络地址字符串的指针返回。成功则返回字符串指针,失败则返回NULL。
参数:
in
:将被转换的二进制IP地址(即32位无符号整数)
返回值:
成功:返回点分十进制的IP地址
失败:返回NULL
②函数inet_ntop
函数原型:
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
说明:
将二进制的IP地址转换成点分十进制的字符串(即点分十进制的IP地址)
该函数将地址类型协议簇为af的网络地址src转换为字符串,并将其存储到dst中,其中dst不能是空指针。
调用者在参数size中指定可使用的缓冲字节数。
inet_ntop
拓展自inet_ntoa
来支持多种地址类型协议簇,inet_ntoa
现在已经被弃用。
参数:
af
:AF_INET
或AF_INET6
src
:二进制的IP地址
dst
:保存被转换成点分十进制的IP地址
size
:指定可使用的缓冲字节数
返回值:
inet_ntop
执行成功,返回一个指向dst的非空指针,如果执行失败,将返回NULL,并且errno设置为相应的错误类型。
错误类型:
EAFNOSUPPORT
:af并不是一个合法的地址类型协议簇
ENOSPC
:要转换的字符串地址src其字节大小超过了给定的缓冲字节大小
三、Socket套接字相关函数
1、Socket 编程流程
socket编程有以下几种基本函数:
socket():用于创建套接字,同时指定协议和类型
bind():将保存在相应地址结构中的地址信息与套接字进行绑定。主要用于服务器端,客户端创建的套接字可以不绑定地址
listen():在服务器端建立套接字并绑定地址后,将套接字设置成监听模式(被动模式),准备接收客户端的连接请求
accept():等待并接收客户端的连接请求。建立好TCP连接后,该函数将返回一个新的已连接套接字
connect():客户端通过该函数向服务器端的监听套接字发送连接请求
send()和recv():通常用于TCP通讯中的发送和接收数据,也可用在UDP中
sendto()和recvfrom():通常用于UDP通讯中的发送和接收数据
(1)TCP通信流程
服务器:
1.创建套接字(socket)
2.将socket与IP地址和端口绑定(bind)
3.监听被绑定的端口(listen)
4.接收连接请求(accept)
5.从socket中读取客户端发送来的信息(read)
6.向socket中写入信息(write)
7.关闭socket(close)
客户端:
1.创建套接字(socket)
2.连接指定计算机的端口(connect)
3.向socket中写入信息(write)
4.从socket中读取服务端发送过来的消息(read)
5.关闭socket(close)
(2)UDP通信流程
服务器:
1.使用函数socket(),生成套接字文件描述符;
2.通过struct sockaddr_in 结构设置服务器地址和监听端口;
3.使用bind() 函数绑定监听端口,将套接字文件描述符和地址类型变量(struct sockaddr_in )进行绑定;
4.接收客户端的数据,使用recvfrom() 函数接收客户端的网络数据;
5.向客户端发送数据,使用sendto() 函数向服务器主机发送数据;
6.关闭套接字,使用close() 函数释放资源;
客户端:
1.使用socket(),生成套接字文件描述符;
2.通过struct sockaddr_in 结构设置服务器地址和监听端口;
3.向服务器发送数据,sendto() ;
4.接收服务器的数据,recvfrom() ;
5.关闭套接字,close() ;
2、Socket 相关函数
(1)socket()函数(创建套建字)
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
说明:
用于创建套接字,同时指定协议和类型
参数:
domain
:指明所使用的协议族,通常为AF_INET
,表示互联网协议族(TCP/IP协议族);
AF_INET
IPv4因特网域
AF_INET6
IPv6因特网域
AF_UNIX
Unix域
AF_ROUTE
路由套接字
AF_KEY
密钥套接字
AF_UNSPEC
未指定
type
:参数指定socket的类型:
SOCK_STREAM
: 流式套接字提供可靠的,面向连接的通信流,它使用TCP协议,从而保证了数据传输的正确性和顺序性
SOCK_DGRAM
:数据报套接字定义了一种无连接的服,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠,无差错的。它使用数据报协议UDP
SOCK_RAW
:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
protocol
:通常赋值为“0”
0
选择type类型对应的默认协议
IPPROTO_TCP
TCP传输协议
IPPROTO_UDP
UDP传输协议
IPPROTO_SCTP
SCTP传输协议
IPPROTO_TIPC
TIPC传输协议
返回值:
成功返回非负套接字描述符,失败返回-1
(2)bind()函数(绑定套建字)
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
说明:
用于绑定IP地址和端口号到socket
参数介绍:
sockfd
:是一个socket描述符
addr
:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同。
addrlen
:地址的长度,一般用sizeof(struct sockaddr_in)
表示
补充:
sockaddr
在头文件#include <sys/socket.h>
中定义,
sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了,如下:
struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
sockaddr_in
在头文件#include<netinet/in.h>
或#include <arpa/inet.h>
中定义,
该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下:
struct sockaddr_in {
sa_family_t sin_family;/*协议族*/
in_port_t sin_port;/*端口号*/
struct in_addr sin_addr;/*IP地址结构体*/
unsigned char sin_zero[8];/*填充没有实际意义只是为跟sockaddr结构在内存中对齐这样两者才能相互转换*/
由上面两个结构体可以看出我们在实际编程的时候通常是对第二个结构体进行使用,由于struct sockaddr数据结构类型不方便设置,所以通常会通过对struct sockaddr_in进行地质结构设置,然后进行强制类型转换成struct sockaddr类型的数据。
返回值:
若成功,返回0;若出错,返回-1
(3)listen()函数(监听被绑定的端口)
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
说明:
设置能处理的最大连接数,listen并未开始接受连线,只是设置了socket的listen模式,listen函数只用于服务器端,服务器进程不知道要与谁进行连接,因此,它不会主动的要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接,主要就连个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。
内核为任何一个给定监听套接字维护两个队列:
未完成连接队列,每个这样的SYN报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程,这些套接字处于SYN_REVD
状态
已完成连接队列,每个已完成TCP三次握手过程的客户端对应其中一项,这些套接字处于ESTABLISHED
状态。
参数介绍:
sockfd
:socket系统调用返回的服务端socket描述符
backlog
:指定在请求队列中允许的最大的请求数,大多数系统默认为5
返回值:
若成功,返回0;若出错,返回-1
(4)accept()函数(接收连接请求)
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
说明:
accept函数由TCP服务器调用,用于从已完成连接队列对头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠。
参数介绍:
sockfd
:是socket系统调用返回的服务器端socket描述符
addr
:用来返回已连接的对端(客户端)的协议地址
addrlen
:客户端地址长度,注意需要取地址
返回值:
该函数的返回值是一个新的套接字的描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符,一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示TCP三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。若出错,返回-1
(5)connect()函数(发送连接请求)
函数原型:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
说明:
该函数用于绑定之后的client端(客户端),与服务器建立连接
参数介绍:
sockfd
:创建的socket描述符
addr
:服务端的ip地址和端口号的地址结构指针
addrlen
:地址的长度,通常被设置为sizeof(struct sockaddr)
返回值:
成功返回0,遇到错误时返回-1,并且errno中包含相应的错误码
(6)send()、recv()函数(TCP发送、接收信息)
①函数send()
函数原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
说明:
函数只能对处于连接状态的套接字进行使用,参数sockfd为已建立好连接的套接字描述符
参数介绍:
sockfd
:为已建立好连接的套接字描述符即accept函数的返回值
buf
:要发送的内容
len
:发送内容的长度
flags
:设置为MSG_DONTWAITMSG
时表示非阻塞,设置为0
时功能和write()
一样\
返回值:
成功返回实际发送的字节数,失败:返回 -1
②函数recv()
函数原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
说明:
接收套接字中的数据
参数介绍:
sockfd
:在哪个套接字接
buf
:存放要接收的数据的首地址
len
:要接收的数据的字节
flags
:设置为MSG_DONTWAITMSG
时表示非阻塞,设置为0
时功能和read()
一样
注意:read()
是一个阻塞函数,如果客户端没有声明断开,那么它就会认为客户端仍旧可能发送数据,所以就会一直阻塞而不是返回-1;
返回值:
成功返回实际发送的字节数,失败:返回 -1
(7)sendto()、recvfrom()函数(UDP发送、接收消息)
①函数sendto()
函数原型:
#include <sys/types.h>
#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);
说明:
UDP发送消息
参数介绍:
sockfd
: socket描述符
buf
:UDP数据报缓存区(包含待发送数据)
len
: UDP数据报的长度
flags
:调用方式标志位(一般设置为0),设置为MSG_DONTWAITMSG
时表示非阻塞
dest_addr
:IP地址和端口号,指向接收数据的主机地址信息的结构体(sockaddr_in
需类型转换);之前介绍bind
函数时已经介绍
addrlen
:dest_addr所指结构体的长度
返回值:
成功则返回实际传送出去的字符数,失败返回-1,错误原因会存于errno 中
②函数recvfrom()
函数原型:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
说明:
UDP接收消息
参数介绍:
sockfd
:socket描述符
buf
:UDP数据报缓存区(包含所接收的数据)
len
:缓冲区长度
flags
:调用操作方式(一般设置为0),设置为MSG_DONTWAITMSG
时表示非阻塞
src_addr
:IP地址和端口号,指向发送数据的客户端地址信息的结构体(sockaddr_in
需类型转换)
addrlen
:指针,指向src_addr结构体长度值。
返回值:
成功则返回实际接收到的字符数,失败返回-1,错误原因会存于errno 中
四、示例
代码:
//server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int sockfd;
int client_fd;
int n_read;
char readBuf[128];
//char *msg = "I get your message";
char writeBuf[128] = {0};
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc != 3)
{
printf("the param is not good!\n");
exit(1);
}
//void *memset(void *s, int c, size_t n);
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.int socket(int domain, int type, int protocol);
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1){
perror("socket");
exit(1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));//uint16_t htons(uint16_t hostshort);
inet_aton(argv[1],&s_addr.sin_addr); //int inet_aton(const char *cp, struct in_addr *inp);
//2.int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
bind(sockfd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.int listen(int sockfd, int backlog);
listen(sockfd,10);
//4.int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int c_len = sizeof(struct sockaddr_in);
while(1){
client_fd = accept(sockfd,(struct sockaddr *)&c_addr,&c_len);
if(client_fd == -1){
perror("accept");
exit(1);
}
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));//char *inet_ntoa(struct in_addr in);
if(fork() == 0){
if(fork() == 0){
while(1){
//6.write
memset(writeBuf,0,sizeof(writeBuf));
printf("input: ");
gets(writeBuf);
write(client_fd,writeBuf,strlen(writeBuf));
}
}
while(1){
//5.read
memset(readBuf,0,sizeof(readBuf));
n_read = read(client_fd,readBuf,128);
if(n_read == -1){
perror("read");
exit(1);
}else{
printf("get %d message:%s\n",n_read,readBuf);
}
}
break;
}
}
return 0;
}
//client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int client_fd;
int n_read;
char readBuf[128];
//char *msg = "message from client";
char writeBuf[128] = {0};
struct sockaddr_in c_addr;
if(argc != 3)
{
printf("the param is not good!\n");
exit(1);
}
//void *memset(void *s, int c, size_t n);
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.int socket(int domain, int type, int protocol);
client_fd = socket(AF_INET,SOCK_STREAM,0);
if(client_fd == -1){
perror("socket");
exit(1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));//uint16_t htons(uint16_t hostshort);
inet_aton(argv[1],&c_addr.sin_addr); //int inet_aton(const char *cp, struct in_addr *inp);
//2.int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
if(connect(client_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(1);
}
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));//char *inet_ntoa(struct in_addr in);
while(1){
if(fork() == 0){
while(1){
memset(writeBuf,0,sizeof(writeBuf));
printf("input: ");
gets(writeBuf);
//3.write/send
write(client_fd,writeBuf,strlen(writeBuf));
}
}
while(1){
//4.read
memset(readBuf,0,sizeof(readBuf));
n_read = read(client_fd,readBuf,128);
if(n_read == -1){
perror("read");
exit(1);
}else{
printf("get %d message from server:%s\n",n_read,readBuf);
}
}
}
return 0;
}
这里用到了memset函数,补充博客推荐:C语言中menset()函数
结果:
参考:Linux系统编程——网络编程
最后谢谢阅读,笔者乃小白,如有错误之处还请指正。