1.socket的相关概念和思路
1.socket的功能和实现
功能:为不同机器上的两个进程提供通信机制。
实现:在硬件层通过网络设备连接,在软件上通过标准的网络协议集TCP或UDP。
2.socket实现功能的思路
主要考虑面向连接的套接字编程方法,将其与打电话的过程进行类比。假设有一个人开通了一个电话号码,决定专门办理某种业务,那么他需要做的事情主要有:
买一个电话:相当于调用socket()
函数,创建一个套接字
绑定电话号码:通过bind()
函数绑定自己的IP地址
等待接听:使用listen()
函数等待网络连接
接听电话:accept()
函数接受连接
传输信息:recv()
收到消息,send()
发送消息
挂掉电话:close()
函数关闭连接
如果有一个人打算电话办理业务,那么他需要:
买一个电话:还是调用socket()
函数
打电话:connect()
联系客服
传输信息:recv()
和send()
分别收到和发送消息
挂掉电话:close()
关闭连接
2.分析程序
由于还没学习线程的内容,目前的程序只能做一件事情,所以一端只接收,一端只发送。
先看部分结构和函数分析。
1.struct sockaddr_in sin_addr, pin_addr;
sockaddr_in
是一个数据结构,具体为:
struct sockaddr_in {
short int sin_family; //2
unsigned short int sin_port; //2
struct in_addr sin_addr; //4
unsigned char sin_zero[8]; //8
};
sin_family
代指协议族,在socket编程中只能使用AF_INET。AF_INET是IPv4网络协议的套接字类型。
sin_port
存储端口号。一般计算机存储数据时有大端小端之别,高地址存放数据的尾端称为大端,低地址存放数据的尾端称为小端。为保证在网络上数据传输不出现错误,定义Internet上以大端模式进行传输。所以对于小端存放数据的机器,就需要进行转换。为保证操作的方便性,对于所有机器都进行转换。
sin_addr
存放IP地址,使用in_addr
数据结构。
sin_zero
是为了让sockaddr
和sockaddr_in
两个数据结构保持大小相同而保留的空字节。sockaddr
是通用的网络地址结构,为一般函数原型采用。sockaddr_in
是Internet网络的地址结构,编写程序时使用,在传递参数时转化为sockaddr
类型。
sockaddr
结构:
struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
2.memset(&sin_addr, 0 , sizeof(sin_addr));
将sin_addr
的内容全部置零,注意memset
函数位于头文件<string.h>
中。
3.
sin_addr.sin_family = AF_INET;
sin_addr.sin_addr.s_addr = htonl(INADDR_ANY);
sin_addr.sin_port = htons(PORT);
第一步,赋值协议族。
第二步,涉及到in_addr的结构:
struct in_addr {
in_addr_t s_addr;
};
所以s_addr
就表示IPv4地址。
INADDR_ANY就是指定地址为0.0.0.0,这个地址表示不确定地址或者所有地址、任意地址。如果服务器有多个IP地址,只绑定其中一个地址,就只能收到发送给一个地址的信息。而如果绑定0.0.0.0,就能收到所有IP地址收到的信息了。
htonl()
是将32位的数据由主机字节序转化为网络字节序。
第三步,htons()
的作用是将16位端口号由主机字节序转化为网络字节序。
4.recv_sockfd = socket(AF_INET, SOCK_STREAM, 0)
socket函数原型:int socket(int domain, int type, int protocol)
所在的库:#include <sys/types.h>
,#include <sys/socket.h>
返回值:
若函数调用成功,则返回一个标识该套接字的文件描述符,失败则返回-1。
domain
用于设置网络通信的域,socket()根据该参数选择通信协议的族。应该为AF_INET。
type
用于设置套接字通信的类型。其中SOCK_STREAM为流式套接字。Tcp连接,提供序列化的、可靠的、双向连接的字节流。流式套接字在数据收发之前必须已经连接。
protocol
用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样该参数仅能设置为0。
5.bind(recv_sockfd, (struct sockaddr*) &sin_addr, sizeof(sin_addr))
bind函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
所在库:#include <sys/types.h>
,#include <sys/socket.h>
返回值:
成功,返回0;不成功,可能返回-1。
若函数调用成功,则socket将绑定特定地址。
recv_sockfd
文件描述符。
addr
,一个const struct sockaddr *
类型的指针,指向要绑定给socket的协议地址。
6.listen(recv_sockfd, 5) < 0
函数原型:int listen(int sockfd, int backlog)
所在库:#include <sys/types.h>
,#include <sys/socket.h>
返回值:
成功,返回0;不成功,可能返回-1。
backlog
为相应socket可以排队的最大连接个数。若连接数已经达到最大,当有客户端尝试connect()时,就会出现问题。
7.new_sockfd = accept(recv_sockfd, (struct sockaddr *) &pin_addr,&pin_addr_size)
accept函数原型为:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
struct sockaddr *addr
,用于返回客户端的协议地址。
最后一个参数为客户端的地址长度。
8.recv(new_sockfd, buf, 200, 0)
函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
返回值:如果返回值为0表示已经读到文件结束,小于零表示出现了错误。
服务端程序:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#define PORT 5559
int main(int argc, char **argv)
{
//定义传输信息存储空间
char buf[200];
//定义服务端套接字描述符,客户端套接字描述符
int recv_sockfd,new_sockfd;
//定义带有服务端、客户端IP地址的数据结构
struct sockaddr_in sin_addr, pin_addr;
int len, pin_addr_size;
pin_addr_size = sizeof(struct sockaddr);
//给sin_addr进行赋值
memset(&sin_addr, 0 , sizeof(sin_addr));
sin_addr.sin_family = AF_INET;//协议族
sin_addr.sin_addr.s_addr = htonl(INADDR_ANY);//0.0.0.0转化网络字节序
sin_addr.sin_port = htons(PORT);//端口号转化网络字节序
//得到服务端套接字描述符
if ((recv_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
//服务端套接字描述符与IP地址绑定
if (bind(recv_sockfd, (struct sockaddr*) &sin_addr, sizeof(sin_addr)) < 0)
{
perror("bind");
exit(1);
}
//服务端套接字描述符开始接听信息
if (listen(recv_sockfd, 5) < 0)
{
perror("listen");
exit(1);
}
//始终准备连接,如果连接成功则始终等待接受消息
while(1)
{
if ((new_sockfd = accept(recv_sockfd, (struct sockaddr *) &pin_addr,&pin_addr_size)) < 0)
{
perror("accept");
exit(1);
}
while (1)
{
if (recv(new_sockfd, buf, 200, 0) == -1)
{
perror("recv");
exit(1);
}
printf("received from client :%s\n", buf);
memset(buf,'\0',sizeof(buf));
sleep(1);
}
close(new_sockfd);
}
return 0;
}
客户端程序:
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#define PORT 5559
char str[20];
int main(int argc, char **argv)
{
//定义传输信息存储空间
char buf[200];
char msg[256];
//定义发送的套接字描述符
int send_sockfd;
//定义带有服务端IP地址的结构
struct sockaddr_in pin_addr;
//对于pin_addr开始赋值
bzero(&pin_addr, sizeof(pin_addr));
pin_addr.sin_family = AF_INET;
//inet_addr将点分10进制的IP地址转换为长整数类型
pin_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
pin_addr.sin_port = htons(PORT);
//创建套接字描述符
if ((send_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
//连接服务端
if (connect(send_sockfd, (void*) &pin_addr, sizeof(pin_addr)) == -1)
{
perror("connect");
exit(1);
}
//始终等待发送数据
while(1)
{
printf("please type msg:\n");
scanf("%s",&str);
if (send(send_sockfd, str, strlen(str), 0) == -1)
{
perror("send");
exit(1);
}
}
close(send_sockfd);
return 0;
}