基于Socket实现一对一通信1
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Linux下的Socket编程实现一对一通信
田昕煜
(1.通信工程,13081405)
摘要:以当下社会普遍应用QQ,微信,微博等聊天工具为引,设计了简单的Linux系统下的socket 编程实现客户端与服务器的一对一通信。
关键词:多线程,socket创建,bind函数,connect函数,listen函数,accept函数,数
据传输
0基本原理
本聊天室程序在 Ubuntu 下,采用 C 语言实现,结构为 Client/Server 结构;服务端程序通过共享存储区存储聊天数据,并发送给每个连接的客户端;服务端程序和客户端程序都是通过父子进程分别负责发送和接收数据的,以避免数据冲撞;
1设计思路及功能
1.1总体设计框架
1.2程序设计方案
Socket创建
socket函数原型为:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:调用成功,返回socket文件描述符;失败,返回-1,并设置errno
参数说明:
domain指明所使用的协议族,通常为PF_INET,表示互联网协议族(TCP/IP协议族;
type参数指定socket的类型:
SOCK_STREAM 提供有序、可靠、双向及基于连接的字节流
SOCK_DGRAM 支持数据报
SOCK_SEQPACKET 提供有序、可靠、双向及基于连接的数据报通信
SOCK_RAW 提供对原始网络协议的访问
SOCK_RDM 提供可靠的数据报层,但是不保证有序性
protocol通常赋值"0".
socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。
调用socket 函数时,socket执行体将建立一个socket,实际上"建立一个socket"意味着为一个socket 数据结构分配存储空间。
socket执行体为你管理描述符表。
两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。
socket数据结构中包含这五种信息。
Socket绑定
bind函数原型为:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sock_fd,struct sockaddr *my_addr, int addrlen);
功能说明:将套接字和指定的端口相连。
成功返回0,否则,返回-1,并置errno.
参数说明:sock_fd是调用socket函数返回的socket描述符,
my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;
addrlen常被设置为sizeof(struct sockaddr)。
struct sockaddr结构类型是用来保存socket信息的:
struct sockaddr {
unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字节的协议地址 */
};
sa_family一般为AF_INET,代表Internet(TCP/IP)地址族;
sa_data则包含该socket的IP地址和端口号。
另外还有一种结构类型:
struct sockaddr_in {
short int sin_family; /* 地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */ };
这个结构更方便使用。
sin_zero用来将sockaddr_in结构填充到与struct sockaddr同样的长度,可以用bzero()或memset()函数将其置为零。
指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的时候将一个指向 sockaddr_in的指针转换为指向sockaddr的指针;或者相反。
使用bind函数时,可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号:
my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */
my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */
通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。
同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。
注意在使用bind函数是需要将sin_port和sin_addr转换成为网络字节优先顺序。
计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先(大端和小端)。
Internet上数据以高位字节优先顺序在网络上传输,所以对于在内部是以低位字节优先方式存储数据的机器,在Internet上传输数据时就需要进行转换,否则就会出现数据不一致。
Bind()函数在成功被调用时返回0;出现错误时返回"-1"并将errno置为相应的错误号。
需要注意的是,在调用bind函数时一般不要将端口号置为小于1024的值,因为1到1024是保留端口号,你可以选择大于1024中的任何一个没有被占用的端口号。
连接
面向连接的客户程序使用connect函数来配置socket并与远端服务器建立一个TCP连接,其函数原型为:
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
功能说明:客户端发送服务请求。
成功返回0,否则返回-1,并置errno。
参数说明:sock_fd 是socket函数返回的socket描述符;serv_addr是包含远端主机IP 地址和端口号的指针;addrlen是远端地质结构的长度。
进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心, socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候到端口。
connect函数启动和远端主机的直接连接。
只有面向连接的客户程序使用socket时才需要将此socket与远端主机相连。
无连接协议从不建立直接连接。
面向连接的服务器也从不启动一个连接,它只是被动的在协议端口监听客户的请求。
监听
listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。
int listen(int sock_fd, int backlog);
功能说明:等待指定的端口的出现客户端连接。
调用成功返回0,否则,返回-1,并置errno.
参数说明:sock_fd 是socket系统调用返回的socket 描述符;
backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept() 接受
accept()函数让服务器接收客户的连接请求。
在建立好输入队列后,服务器就调用accept 函数,然后睡眠并等待客户的连接请求。
int accept(int sock_fd, void *addr, int *addrlen);
功能说明:用于接受客户端的服务请求,成功返回新的套接字描述符,失败返回-1,并置errno。
参数说明:sock_fd是被监听的socket描述符,addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求);addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。
出现错误时accept函数返回-1并置相应的errno值。
首先,当accept函数监视的 socket收到连接请求时,socket执行体将建立一个新的socket,执行体将这个新socket和请求连接进程的地址联系起来,收到服务请求的初始socket仍可以继续在以前的 socket上监听,同时可以在新的socket描述符上进行数据传输操作。
结束传输
当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:
2关键代码解释
客户端:
#include <stdio.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#define LISTEN_LEN 10
int err,sd;
char rd_buf[100], wr_buf[100];
void *thread_read1(void *arg)
{
while(1)
{
memset(rd_buf,0,100);
read(sd,rd_buf,100);
printf("%s\n",rd_buf);
sleep(2);
}
return NULL;
}
void *thread_write1(void *arg)
{
while(1)
{
memset(wr_buf, 0, 100);
scanf("%s", wr_buf);
write(sd,wr_buf,sizeof(wr_buf));
sleep(2);
}
}
int main()
{
pthread_t tid1,tid2;
int server_len,client_len;
struct sockaddr_in server_ip,client_ip;
sd = socket(AF_INET,SOCK_STREAM,0);
if(sd < 0)
{
printf("socket failed! errno = %d\n",errno);
close(sd);
return -1;
}
server_ip.sin_family = AF_INET;
server_ip.sin_port = htons(5678);
server_ip.sin_addr.s_addr = htonl(INADDR_ANY);
memset(server_ip.sin_zero,0,8);
err = connect(sd,(struct sockaddr *)(&server_ip),sizeof(struct sockaddr));
if(err < 0)
{
printf("connect failed! errno = %d\n",errno);
close(sd);
return -1;
}
err = pthread_create(&tid1,NULL,thread_read1,NULL);
if(err != 0)
{
printf("thread_read1 creat failed! errno = %d\n",errno);
close(sd);
}
err = pthread_create(&tid2,NULL,thread_write1,NULL);
if(err != 0)
{
printf("thread_write1 creat failed! errno = %d\n",errno);
close(sd);
return -1;
}
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
close(sd);
return 0 ;
服务器:
#include <stdio.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#define LISTEN_LEN 10
int i=0,err,sd,ad;
char buf12[100],buf21[100];
void *thread_read1(void *arg);
void *thread_write1(void *arg);
void *thread_read1(void *arg)
{
while(1)
{
read(ad,buf12,100);
printf("%s\n", buf12);
sleep(2);
}
return NULL;
}
void *thread_write1(void *arg)
{
while(1)
{
memset(buf21, 0, 100);
scanf("%s", buf21);
write(ad,buf21,100);
sleep(2);
}
return NULL;
}
int main()
{
pthread_t tid1,tid2;
int server_len,client_len;
struct sockaddr_in server_ip,client_ip;
sd = socket(AF_INET,SOCK_STREAM,0);
if(sd < 0)
{
printf("socket failed! errno = %d\n",errno);
close(sd);
return -1;
}
server_ip.sin_family = AF_INET;
server_ip.sin_port = htons(5678);
server_ip.sin_addr.s_addr = htonl(INADDR_ANY);
memset(server_ip.sin_zero,0,8);
err = bind(sd,(struct sockaddr *)(&server_ip),sizeof(struct sockaddr)); if(err < 0)
{
printf("bind failed! errno = %d\n",errno);
close(sd);
return -1;
}
err = listen(sd,LISTEN_LEN);
if(err < 0)
{
printf("listen failed! errno = %d\n",errno);
close(sd);
return -1;
}
client_len = sizeof(struct sockaddr);
while(1){
ad = accept(sd,(struct sockaddr *)(&client_ip),&client_len);
if(ad < 0 )
{
printf("accept failed! errno = %d\n",errno);
close(sd);
return -1;
}
err = pthread_create(&tid1,NULL,thread_read1,NULL);
if(err != 0)
{
printf("thread_read1 creat failed! errno = %d\n",errno);
close(sd);
close(ad);
return -1;
}
err = pthread_create(&tid2,NULL,thread_write1,NULL);
if(err != 0)
{
printf("thread_write1 creat failed! errno = %d\n",errno);
close(sd);
close(ad);
return -1;
}
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(ad);
close(sd);
return 0 ;
}
3运行测试结果
4结论
这是一个很简单又很复杂的过程,从来没有接触过虚拟机,开始连软件都不会打开,逐渐学会了使用linux编程。
通过老师的帮助,查资料学习最后进行相应的修改。
致谢
本次实验由于对软件和linux编程都不了解,并非个人独立完成,系我与李倩同学一起交流完成,她给了我非常大的帮助。
非常感谢老师们总是不厌其烦的给我们讲解实验过程中的不懂之处以及课上在实验室的指导。
这次实验从什么都不懂到做出成果其中有不少曲折,感谢老师和研究生学长学姐们不断的给我们鼓励,并提出程序上的不足,使我们能有进一步改进设计的正确方向。
衷心祝愿老师学长学姐们工作顺利,心想事成!
参考文献
【1】章坚武,李杰,姚英彪.嵌入式系统设计与开.西安:西安电子科技大学出版社,2009.
【2】科默,史蒂文. 用TCP/IP进行网际互连.北京:电子工业出版社.2013 【3】郭德响. 一种开放式数控系统的研究与应用[D]. 江苏大学2009
【4】吴长忠. 面向网络化制造开放式数控系统的研究[D]. 山东大学2008 【5】杨路明. C语言程序设计. 北京:北京邮电大学出版社,2005.
【6】谢希仁. 计算机网络. 北京:电子工业出版社,2008.
【7】陈更力,张青.基于JavaSocket网络编程的一种新实现 [J].电脑开发与用,2006.。