首先需要在Ubuntux86上运行服务器程序,然后在开发板上运行客户端来实现。
下面分别将x86服务器程序与开发板客户端程序做一下代码原理笔记,方便查阅复习:
1、服务器端程序代码:
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
int main()
{
int sfp, nfp, num = 0;
struct sockaddr_in s_add,c_add;//sockaddr_in 在netinet/in.h定义
int sin_size;
unsigned short portnum=0x8888;
char buffer[100] = {0};
printf("Hello,welcome to my server !\r\n");
//socket为创建一个通信端点,返回一个描述符
sfp = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sfp)//出错误
{
printf("socket fail ! \r\n");//打印失败
return -1;//表示该函数失败
}
printf("socket ok !\r\n");//成功的话打印socket成功
//bzero;置字节字符串前n个字节为零且包括‘\0’。变量清零。
bzero(&s_add,sizeof(struct sockaddr_in));
//sizeof() 是一种内存容量度量函数,功能是返回一个变量或者类型的大小(以字节为单位);
//给清零后的s_add结构体赋值;
s_add.sin_family=AF_INET;//使用IPv4协议
s_add.sin_addr.s_addr=htonl(INADDR_ANY);//允许任何地址
s_add.sin_port=htons(portnum);//设置端口号
//然后调用bind绑定函数,使用的是IPv4 协议族
if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
{
printf("bind fail !\r\n");//出错打印失败
return -1;//表示该函数失败
}
printf("bind ok !\r\n");//bind成功打印
//调用监听函数,监听到用户的请求,设置队列数
if(-1 == listen(sfp,5))
{
printf("listen fail !\r\n");//错误打印
return -1;
}
printf("listen ok\r\n");//成功打印
//大小长度幅值,单位字节
sin_size = sizeof(struct sockaddr_in);
//调用accept函数接受请求,重新分配套接字,不影响socket创建的套接字
nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);
if(-1 == nfp)
{
printf("accept fail !\r\n");//错误打印
return -1;
}
printf("accept ok!\r\nServer start get connect from %#x : %#x\r\n",
ntohl(c_add.sin_addr.s_addr), ntohs(c_add.sin_port));//成功打印,后边的是一个转换,可man一下查看原型
while(1)
{
memset(buffer, 0, 100);//清零
sprintf(buffer, "hello,welcome to my server(%d) \r\n", num++);//发送到buffer中,sprintf把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串。
send(nfp, buffer, strlen(buffer), 0);//send函数,功能是向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR。
usleep(500000);
}
//调用close 关闭套接字
close(nfp);
close(sfp);
return 0;
}
阅读的时候首先就看到一个socket
函数,socket()为通信创建一个套接字并返回一个描述符。函数原型为:int socket(int domain, int type, int protocol);
第一个参数:domain
参数指定通信域;这将选择将用于通信的协议族。这些族在<sys/socket.h>中定义。目前理解的格式包括:
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7) //本地通信
AF_INET IPv4 Internet protocols ip(7) //IPv4互联网协议
AF_INET6 IPv6 Internet protocols ipv6(7) //IPv6互联网协议
AF_IPX IPX - Novell protocols //IPX-Novell协议
AF_NETLINK Kernel user interface device netlink(7) //核心用户界面设备
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7) //ITU-T X.25/ISO-8208协议
AF_AX25 Amateur radio AX.25 protocol //业余无线电AX.25协议
AF_ATMPVC Access to raw ATM PVCs //访问原始ATM PVC
AF_APPLETALK Appletalk ddp(7) //苹果计算机公司设计出来的通信协议系列;可路由协议组;协议;Mac机所用的网络协议之一;地址解析协议;
AF_PACKET Low level packet interface packet(7) //低层分组接口
第二个参数:type
;套接字具有指定通信语义的指示类型。当前定义的类型是:
SOCK_STREAM 提供顺序的、可靠的、双向的、基于连接的字节流。可以支持带外数据传输机制。表示创建的是TCP连接;
SOCK_DGRAM 支持数据报(固定最大长度的无连接、不可靠消息)。
SOCK_SEQPACKET 为固定最大长度的数据报提供一个顺序的、可靠的、基于双向连接的数据传输路径;用户需要在每次输入系统调用时读取整个数据包。
SOCK_RAW 提供原始网络协议访问。
SOCK_RDM 提供不保证排序的可靠数据报层。
SOCK_PACKET 已过时,不应在新程序中使用; 参见数据包(7)。packet(7).
从Linux 2.6.27开始,type参数具有第二个目的:除了指定套接字类型,它还可以包含以下任意值的按位“或”,以修改socket()的行为:
SOCK_NONBLOCK 在新的打开文件描述上设置O_NONBLOCK文件状态标志。 使用此标志可以节省对fcntl(2)的额外调用,以实现相同的结果。
SOCK_CLOEXEC 在新文件描述符上设置执行时关闭(FD_CLOEXEC)标志。 有关为何可能有用的原因,请参见open(2)中O_CLOEXEC标志的描述。
第三个参数:protocol
;该协议指定要与套接字一起使用的特定协议。 通常,在给定协议族中,只有一个协议可以支持特定的套接字类型,在这种情况下,协议可以指定为0。但是,可能存在许多协议,在这种情况下,必须在其中指定一个特定的协议。 方式。 使用的协议号特定于要进行通信的“通信域”;protocols(5)。 有关如何将协议名称字符串映射到协议编号的信息,请参见get-protoent(3)。本次代码里以0忽略。
返回值:int类型;成功后,将返回新套接字的文件描述符。 如果出错,则返回-1,并正确设置errno。
接着遇到bzero
,做一个小复习,函数原型:void bzero(void *s, size_t n);将以s开头的区域的前n个字节设置为零(包含’\ 0’的字节)。第一参数是对象的地址,第二个是字节数。
接着往后阅读,碰到一个sockaddr_in
的结构体。sockaddr_in 在netinet/in.h有定义
struct sockaddr_in{
short int sin_family; //地址族(Address Family)
unsigned short int sin_port; //16位 TCP/UDP端口号
struct in_addr sin_addr; //32位 IP地址
char sin_zero[8]; //不使用,对齐作用
};
其中在赋值中有一个设置端口号的函数htons
复习一下;函数原型uint16_t htons(uint16_t hostshort);
该函数将无符号的短整数hostshort从主机字节顺序转换为网络字节顺序。
接着往后阅读碰到了一个bind
绑定函数,该函数的原型为int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第一个参数为sockfd,为socket
后的返回值。
第二个参数:没看太懂,可以参照该函数的一个man 2 bind
中的示例来写就可以了,没必要太纠结,毕竟它只是个工具。
第三个参数:大小长度,单位字节
返回值:成功时,返回零。 如果出错,则返回-1,并正确设置errno。
接着往后阅读,碰到一个listen
监听函数;函数原型为int listen(int sockfd, int backlog);
该函数将sockfd(相当于本代码里的sfp
)所引用的套接字标记为被动套接字,即,该套接字将用于使用accept(2)接受传入的连接请求。也就是一直在监听socket
函数。
第一个参数:sockfd参数是文件描述符,它引用类型为SOCK_STREAM或SOCK_SEQ-PACKET的套接字。这里是sfp即socket的返回值;
第二个参数:backlog参数定义sockfd的未连接队列(也就是没有连接还在等待的队列)可以增长到的最大长度。 如果在队列已满时连接请求到达,则客户端可能会收到带有ECONNREFUSED指示的错误,或者,如果基础协议支持重新传输,则可以忽略该请求,以便以后在连接时重新尝试成功。
返回值:成功时,返回零。 如果出错,则返回-1,并正确设置errno。
接着往后阅读遇到一个accept
函数,函数原型为int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
作用就是接受请求;该函数accept
系统调用与基于连接的套接字类型(SOCK_STREAM,SOCK_SEQPACKET)一起使用。 它为侦听套接字sockfd提取未连接队列上的第一个连接请求,创建一个新的已连接套接字,并返回引用该套接字的新文件描述符。 新创建的套接字未处于侦听状态。 原始套接字sockfd不受此调用的影响。
第一个参数为sockfd,为socket
后的返回值。
第二个参数:没看太懂,可以参照该函数的一个man 2 bind
中的示例来写就可以了,没必要太纠结,毕竟它只是个工具。
第三个参数:大小长度,单位字节
返回值:成功时,这些系统调用将返回一个非负整数,该整数是已接受套接字的描述符。 如果出错,则返回-1,并正确设置errno。
如果你看一遍代码后再阅读一遍我的写的文章你就可以形成一个完整的逻辑步骤;首先就是创建一个套接字,然后绑定,然后监听,监听到后接受申请重新分配套接字,循环发送,完成后关闭;下面就是客户端程序的阅读和自我的一些复习笔记;
2、开发板上客户端程序
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc, char **argv)
{
int cfd;
int recbyte;
int sin_size;
char buffer[1024] = {0};
struct sockaddr_in s_add, c_add;
unsigned short portnum = 0x8888;
printf("Hello,welcome to client!\r\n");
//需要引导用户输入服务器IP
if(argc != 2)
{
printf("usage: echo ip\n");
return -1;
}
//socket为创建一个通信端点,返回一个描述符
cfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == cfd)//错误
{
printf("socket fail ! \r\n");//错误打印
return -1;
}
printf("socket ok !\r\n");//socket成功打印
//bzero;置字节字符串前n个字节为零且包括‘\0’。变量清零。
//sizeof() 是一种内存容量度量函数,功能是返回一个变量或者类型的大小(以字节为单位);
//给清零后的s_add结构体赋值;
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;//使用IPv4 协议
s_add.sin_addr.s_addr= inet_addr(argv[1]);//设置要连接的IP 地址(这里是执行程序的时候传递进来的)
s_add.sin_port=htons(portnum);//设置端口号
printf("s_addr = %#x ,port : %#x\r\n",s_add.sin_addr.s_addr,s_add.sin_port);//打印IP地址和端口号
//连接服务器(server)
if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))//错误
{
printf("connect fail !\r\n");//连接失败打印
return -1;
}
printf("connect ok !\r\n");//连接成功打印
//进入循环接受
while(1)
{
//这里就是最基础的了,不废话了,可以man一下看看原型
if(-1 == (recbyte = read(cfd, buffer, 1024)))
{
printf("read data fail !\r\n");//读取失败打印
return -1;
}
printf("read ok\r\nREC:\r\n");//读取成功打印
buffer[recbyte]='\0';//在最后加一个\0
printf("%s\r\n",buffer);//打印
}
//close套接字
close(cfd);
return 0;
}
客户端代码其实跟服务器端的差不多,之前遇到过的就不再重复;
继续往后阅读遇到一个connect
函数,他是来连接服务器的;该函数的原型为int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
该函数系统调用将文件描述符sockfd引用的套接字连接到addr的大小。 addr中地址的格式由套接字sockfd的地址空间决定; 有关更多详细信息,请参见socket(2)。
第一个参数:socket
的返回值,在这里就是cfd;
第二个参数:格式跟之前的bind
函数相似,用就完了,工具而已;
第三个参数:大小长度,单位字节;
返回值:如果连接或绑定成功,则返回零。 如果出错,则返回-1,并正确设置errno。
其实看一次就可以了,以后用到的话不明白知道怎么查怎么用就完了。还是那句话它只是一个工具,我们要做的是利用它去创造别的东西而不是停在这里思考这个工具是怎么加工出来的,或者它是什么牌子的。你说呢?
以后在每篇文章的结尾会写一句我觉的有意义的话吧,这也是为了激励我和看到这篇文章或以后文章的人,我们生下来是平凡的,但是我们可以用我们的双手创造不平凡。
送给阅读过这篇文章的人:远处是风景,近处才是人生。不积跬步无以至千里。
下篇文章见。