目录
一、什么是套接字
二、套接字类型
三 、基于TCP的socket编程
四、相关函数介绍
1、创建套接字——socket()
2、绑定套接字——bind()
3、监听函数——listen()
4、响应连接请求-accept()函数
五、接收数据与发送数据
六、关闭套接字
代码
server.c
头文件:
功能一、初始化socket
功能二、处理客户端请求,返回与连接上的客户信息通信的套接字
功能三、处理客户端连接
功能四主函数调用
client.c
功能函数:ASK 客户端向服务器请求服务
主函数
运行结果
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。Socket的英文原意是“插座”,通常称之为套接字,来描述IP地址和端口,是一个通信链的句柄,用来实现不同虚拟机或者计算机之间的通信。
在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,与不同客户端的不同服务对应着不同的Socket,这样实现了与多个服务器进行不同服务的功能,因为Socket的不同,这些客户端或服务不会混淆。
应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字 (Socket)的接口,区分不同应用程序进程间的网络通信和连接。
流式套接字 SOCK_STREAM
数据报套接字 SOCK_DGRAM
原始套接字 SOCK_RAW
tcp中服务器端的流程如下:
1. 创建套接字(Socket)。
2. 将套接字绑定到一个本地地址和端口上(bind)。
3. 将套接字设为监听模式,准备接受客户请求(listen)。
4. 等待客户请求到来,当请求到来后,接受连接请求。返回一个新的对应于此次连接的套接字(accept)。
5. 用于返回的套接字和客户端进行通信(send/recv)。
6. 返回,等待另一客户请求。
7. 关闭套接字(close)
int socket(int domain, int type, int protocol);
domain
参数domain 指定通信发生的区域,UNIX系统支持的地址族有:AF—UNIX,AF_I,AF—NS等而DOS,WINDOWS
中仅支持AF_INE,他是网际网区域。因此,地址与协议族相同。
The domain argument specifies a communication domain; this selects the protocol family
which will be used for communication. These families are defined in.
The currently understood formats include:Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7)
AF_ALG Interface to kernel crypto API
type 描述要建立的套接字的类型
The socket has the indicated type, which specifies the communication semantics. Cur‐
rently defined types are:SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams.
An out-of-band data transmission mechanism may be supported.SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed
maximum length).SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data trans‐
mission path for datagrams of fixed maximum length; a consumer is
required to read an entire packet with each input system call.SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
Some socket types may not be implemented by all protocol families.
protocol 说明套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。
The protocol specifies a parprticular protocol to be used with the socket. Normally
only a single protocol exists to support a particular socket type within a given pro‐
tocol family, in which case protocol can be specified as 0. However, it is possible
that many protocols may exist, in which case a particular protocol must be specified
in this manner. The protocol number to use is specific to the “communication domain”
in which communication is to take place; see protocols(5). See getprotoent(3) on how
to map protocol name strings to protocol numbers.Sockets of type SOCK_STREAM are full-duplex byte streams. They do not preserve record
boundaries. A stream socket must be in a connected state before any data may be sent
or received on it. A connection to another socket is created with a connect(2) call.
Once connected, data may be transferred using read(2) and write(2) calls or some vari‐
ant of the send(2) and recv(2) calls. When a session has been completed a close(2)
may be performed. Out-of-band data may also be transmitted as described in send(2)
and received as described in recv(2).The communications protocols which implement a SOCK_STREAM ensure that data is not
lost or duplicated. If a piece of data for which the peer protocol has buffer space
cannot be successfully transmitted within a reasonable length of time, then the con‐
nection is considered to be dead. When SO_KEEPALIVE is enabled on the socket the pro‐
tocol checks in a protocol-specific manner if the other end is still alive. A SIGPIPE
signal is raised if a process sends or receives on a broken stream; this causes naive
processes, which do not handle the signal, to exit. SOCK_SEQPACKET sockets employ the
same system calls as SOCK_STREAM sockets. The only difference is that read(2) calls
will return only the amount of data requested, and any data remaining in the arriving
packet will be discarded. Also all message boundaries in incoming datagrams are pre‐
served.
根据这三个参数建立一个套接字,并将相应的资源分配给它,同时,返回一个整型套接字号。
把一个本地协议地址赋予一个套接字,对于网际网协议,协议地址是32位的IPV4地址或者是128位的ipv6与16位的tcp或者UDP端口号的组合。
linux man 文档:
NAME
bind - bind a name to a socketSYNOPSIS(所需头文件)
#include/* See NOTES */
#include函数原型 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 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()操作。
listen函数仅由TCP服务器调用
LISTEN(2) Linux Programmer's Manual LISTEN(2)
NAME
listen - listen for connections on a socketSYNOPSIS(所需头文件)
#include/* See NOTES */
#include函数原型 int listen(int sockfd, int backlog);
函数参数:
sockfd 设定监听的套接字的文件描述符,该套接字必须为SOCK_STREAM类型或SOCK_SEQPACKET类型
backlog 请求队列的最大请求数。若该队列已满,则客户端会收到ECONNREFUSED错误,以便通知客户端在后续重试连接。该值默认为5。函数返回值:
成功:0
失败:-1
完成监听后,服务器会暂时阻塞,等待客户端发来连接请求(由客户端的connect()函数发送),并使用accept()响应该请求。
accept()函数的用法:
函数accept()
所需头文件:#include
#include
函数原型: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
函数返回值:return
成功:建立好连接的套接字文件描述符
失败:-1
在使用socket()、bind()、listen()和accept()后,服务器端与客户端已经成功的建立了通信,此时可以使用send()向对方发送数据,使用recv()接收对方发送的数据。
//除了send()/recv(),有时也可以使用 read()/write() 来接收和发送数据。其中write()可以取代send(),read()可以取代recv()。使用send()/recv()还是read()/write()全凭程序员个人习惯,但是通篇代码使用要统一。
send() 函数的用法:
函数send()
所需头文件:#include
#include
函数原型:int send(int sockfd, const void *buf, size_t len, int flags)
函数参数:
sockfd 需要发送数据的套接字文件描述符
buf 发送数据的缓冲区首地址
len 发送数据的缓冲区长度
flags 通常设定为0。若flags设定为0,则send()与write()等价。
函数返回值:
成功:实际发送的字节数
失败:-1recv()函数的用法:
函数recv()
所需头文件:#include
#include
函数原型:int recv(int sockfd, void *buf, size_t len, int flags)
函数参数:
sockfd 需要接收数据的套接字文件描述符
buf 接收数据的缓冲区首地址
len 接收数据的缓冲区长度
flags 通常设定为0。若flags设定为0,则recv()与read()等价。
函数返回值:
成功:实际接收的字节数
失败:-1
若调用recv()后暂时未收到对方send()的数据,则recv()会阻塞等待直至数据到达。拓展:read() and write()的用法
NAME
read - read from a file descriptor
所需头文件: #include函数原型 : ssize_t read(int fd, void *buf, size_t count);
NAME
write — send a message to another userint write(int sockfd, const void *buf, size_t len)
SYNOPSIS
write user [tty]
若通信双方通信完毕,则需要调用close()关闭双方通信。
函数close()的用法:
函数close()
所需头文件:#include
函数原型:int close(int sockfd);
函数参数:
sockfd 套接字文件描述符
函数返回值:
成功:0
失败:-1
使用TCP通信时连接是双向的(同时可读写),当我们使用close()时会将读写通道都关闭。有时我们只希望关闭一个通道(只读/只写),此时可以使用 shutdown() 来关闭通道。
函数shutdown()的用法:
函数shutdown()
所需头文件:#include
函数原型:int shutdown(int sockfd, int how);
函数参数:
sockfd 套接字文件描述符
how 关闭套接字的指定方向,具体取值如下:
SHUT_RD 0 关闭读,之后无法再继续接收数据
SHUT_WR 1 关闭写,之后无法再继续发送数据
SHUT_RDWR 2 同时关闭读写,此时同close()
函数返回值:
成功:0
失败:-1
好了以上就是网络编程的预备知识,下面让我们开始愉快的敲代码吧 !!
#include
#include
#include
#include
#include
#include
#include
#define PORT 9999 //端口号
功能函数 :(init_socket(), MYACCEPT(), Handle_client(), main(0)
int init_socket()
{
int listen_socket = socket(AF_INET,S OCK_STREAM, 0);
if (listen_socket < 0)
{
perror("socket fail");
return 0;
}
//定义结构体
struct sockaddr_in{
short int sin_family;//Internet 地址族
unsigned short int sin_port;//端口号
struct in_addr sin_addr;//ip地址
unsigned char sin_zero[8];//填0
}
struct sockaddr_in addr;
memeset(&addr,0,sizeof(&addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
//bind本地套接字
int ret = bind(listen_socket,(struct sockaddr*)&addr,sizeof(addr));
if(ret == -1)
{
perror("bind");
return -1;
}
//监听本地套接字
ret = listen(listen ,5)
if(ret == -1)
{
perror("listen");
return -1;
}
printf("等待客户端连接\n");
return listen_sockt
}
int MYACCEPT(int listen_sockt)
{
/*等待客户请求到来,当请求到来后,接受连接请求,返回一个新的对应与此次连接的套接字*/
//接受连接
struct sockaddr_in client_addr;//用来保存客户端的IP和端口信息
int len = sizeof(client_addr);
int client_socket = accept(listen_sockt , (struct sockaddr *)&client_addr ,&len);
if(client_socket < 0)
{
perror("accept fail");
}
printf("成功连接一个客户端:%s\n",inet_ntoa(client_addr.sin_addr));
return client_socket;
}
int Handle_client(int client_socket)
{
char buf[1024];
int i;
while()
{
//从客户端读取数据
int ret = read (client_socket,buuf,1024);
if(ret < 0)
{
perror("read fail");
return -1;
}
//客户端退出
if(ret == 0)
{
printf("客户端退出\n") ;
breadk;
}
buf[ret] = '\0';
for(i=0 ;i
int main()
{
int listen_sockt = init_socket();//返回套接字
while(1)
{
//获取与客户端连接的套接字
int client_socket = MYACCEPT(listen_sockt);
//处理客户端请求
Handle_client(client_socket);
}
close(listen_sockt);
return 0;
}
头文件和server.c一样
//客户端向服务器请求服务(进行通信)
int Ask(int c_socket)
{
char buf[1024];
while(1)
{
fgets(buf,1024,stdin);
if(strncmp(buf,"end",3)== 0)
{
break;
}
write(c_socket,buf,strlen(buf));
int ret =read(c_socket,buf,1023);
if(ret < 0)
{
perror("read");
return -1;
}
printf("%s\n",buf);
}
}
int main()
{
//创建与服务器通信的套接字
int c_socket = socket(AF_INET,SOCK_STREAM,0);
if(c_socket < 0)
{
perror("socket");
return -1;
}
//连接服务器(connect)
//
#if 0
//地址结构
struct sockaddr_in
{
short int sin_family; /* Internet地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /* 填0 */
};
#endif
//将套接字绑定到一个本地地址和端口上
struct sockaddr_in addr;
memset(&addr, 0, sizeof(&addr));
addr.sin_family = AF_INET; //设置地址族
addr.sin_port = htons(PORT); //设置本地端口
//addr.sin_addr.s_addr = htonl(INADDR_ANY); //使用本地任意IP地址
inet_aton("127.0.0.1",&(addr.sin_addr));
//连接服务器成功返回0,失败返回 -1
//成功即可通过c_socket 与服务器进行通信
int ret = connect (c_socket,(struct sockaddr*)&addr,sizeof(addr));
if(ret < 0)
{
perror("connect fail\n");
return -1;
}
printf ("成功连接服务器\n");
//和服务器进行通信(请求服务)
Ask(c_socket);
//关闭套接字
close(c_socket);
return 0;
}
----------------------------------------------------------------------- 编译运行 ---------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------