首页 > 代码库 > socket实现的一个基本点对点聊天程序
socket实现的一个基本点对点聊天程序
多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口。
讯方式。应用程序创建了一个套接字后,就能够获得这种机制提供的网络服务功能。对于服务器来说,它提供
了监听网络的连接请求;对于客户机来说,它可以连接到一个给定的主计算机和特定的端口上。客户端和服务
器端可以通过套接字对象来发送和接收数据。套接字提供了分别基于连接的协议(TCP)等和无连接的协议
(UDP)等,以满足网络连接的可靠性、稳定性以及高速性的要求。
WINSOCK是网络编程接口,它构成了WINDOWS平台下网络编程的基础。
开放系统互连七层模型(OSI)
应用层——表示层——会话层——传输层——网络层——数据链路层——物理层
应用层:用户的应用程序与网络之间的接口
表示层:协商数据交换格式
会话层:允许用户使用简单易记的名称建立连接
传输层:提供终端到终端的可靠连接
网络层:使数据路由经过大型互联网络
数据链路层:决定访问网络介质的方式
物理层:将数据转换为可通过物理介质传送的位
TCP、UDP协议是位传输层的协议,而IP协议则是位于网络层的协议。
TCP是传输控制协议,它是一种面向连接的协议,向用户提供可靠的全双工的字节流。
TCP关心数据传输的准确性。
应用程序利用TCP进行通讯时,发送方和接收方之间会建立一个虚拟连接,通过这一连接,双方可以把数据当作
一个双向的字节流来进行交流。它就像打电话。我们从摘机拨号开始,到拨通后建立连接、进行通话,再到挂
机断开连接这一过程,正是抽象的面向连接的具体表现。首先,在开始通话前,拨号,双方响应,从而建立一
条虚拟的“链路”。只有在双方都处于活动状态,这条“链路”才会保持存在。其次,我们可以通过这条“链
路”进行双向的会话,并且在一般情况下,我们可以通过对方的回答来确定对方是否已经正确听到了我们所说
的话,这相当于面向连接协议为保证传输正确而进行的额外校验。
UDP是用户数据报协议,这是一种无连接的协议。UDP是一种不可靠的数据报协议,它不能保证每一个UDP数据报
可以到达目的地。但是,正是由于它的不可靠性,减少了数据确认的过程,所以UDP传输数据的效率比较高。
就像是邮信。我们只需封好信封,然后将其投到邮筒中即可,但是我们不能保证邮局在把信件发送出去和信件
在发送过程中没有受到伤害。
总体看来,面向连接的服务可以保证双方传递数据的正确性,但却要为此进行额外的校验,通信双方建立通信
信道也需要许多系统开销。而无连接的服务最大的优点就是速度快,因为它不需要去验证数据的完整性,也不
会数据是否已接收而操心。
IP是网际协议,自20世纪80年代以来它一直都是网际协议的主力协议,它使用32位地址,为TCP、UDP、ICMP等
协议提供传送的分组服务。
在WINDOWS网络编程中,套接字接口主要有三种类型:流式套接字、数据报套接字以及原始套接字。
流式套接字定义了一种可靠的面向连接的服务,实现了无差错无重复的顺序数据传输。对于建立在这种流式套
接字类型上的套接字来说,数据可以是双向传输的字节流,无长度限制。
数据报套字接口定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠。
原始套接字允许对低层协议如IP或ICMP直接访问,主要用于网络协议的测试,例如WINDOWS带的PING程序,就是
通过ICMP协议来实现的。
客户/服务器模式
在现在的网络应用中,通信双方最常见的交互模式便是客户/服务器模式。在这种模式下,客户向服务器发出服
务请求,服务器收到请求后为客户提供相应的服务。
客户/服务器模式通常采用监听/连接的方式实现。服务器端应用程序在一个端口监听对服务的请求,也就是说
,服务进程一直处于休眠状态,直到一个客户对这个服务提出了连接请求,此时服务线程被“唤醒”并且为客
户提供服务,即对客户的请求作出适当的反应。
面向连接的协议套接字的调用
面向连接的服务器端首先调用SOCKET()建立一个套接字S,用来监听客户端的连接请求。接着,调用bind()将此
套接字与本机地址、端口绑定起来。然后,调用listen()告诉套字S,对进来的连接进行监听并确认连接请求,
于是S被置于被动的监听模式。一个正在进行监听的套接字将给每个请求发送一个确认信息,告诉发送者主机已
经收到连接请求。当时监听套接字S实际上并不接受连接请求,在客户端请求被接受后,调用
accept()将返回一个与S具有相同属性,但不能被用来进行监听,只用来进行数据收发的数据套接字NS,作为与
客户端套接字相对应的连接的另一个端点。对于该客户端套接字后续的所有操作,都应该通过NS来完成。监听
套接字S仍然用于接收其他客户的连接请求。
面向连接的服务器一般是迸发服务器。在WINDOWS平台上,我们往往在调用accept()返回NS后,会创建一个请求
/应答执行线程,将NS作为参数之一传递给该线程,由该线程来完成客户端与服务器端复杂的请求应答工作,而
主线程会再次调用accept(),以接收新的客户端连接请求。
面向连接的客户端也会调用socket()建立一个套接字C,但使用像TCP这样的面向连接的协议时,客户端不必关
心协议使用什么样的本机地址,所以不用调用bind()。客户端调用connect()向服务器端发出连接请求,在与服
务器建立连接之后,客户端和服务器端就存在了一条虚拟的“管道”,客户端套字C和服务器端套接字NS构成了
“管道”的两个端点。客户端和服务器端通过这个“管道”进行数据交换,多次调用send()/recv()来进行请求
/应答,最终完成服务后关闭用于传输的套接字C和NS,并断开连接,结束此次会话。
面向无连接协议的套接字的调用
采用无连接协议(UDP)时,服务器一般都是面向事务的。一个请求和一个应答就完成了客户程序与服务器程序
之间的相互作用。
无边的服务器使用socket()和bind()来建立和绑定套接字S。与面向连接的服务器端不同,我们不必调用
listen()和accept(),只需要调用recvFrom()在套接字S上等待接收数据就可以了。因为是无连接的,因此网络
上任何一台机器发送的套接字S的数据都可以收到。从这一点上你可以想象,它们是无序的。
无连接的服务器一般都是迭代服务器。它们接收到一个数据报后,马上进行相应处理,直到处理完成后,才开
始下一个数据报的接收、处理。所以采用无连接协议时,客户端和服务器端的交互往往是很简单的,一问一答
或只问不答的方式很常见。
无连接的服务器端只有在停止服务时,才会关闭套接字。
无连接的客户端则更简单,只需要调用socket()建立一个套接字C,就可以利用sendto()和recvfrom()与服务器
的数据进行交换。在完成会话后调用closeSocket()关闭套接字C。
服务器与客户端通过已连接套接字进行接收与发送消息!
p2pcli.c
#include <unistd.h>
#include <sys/types.h>#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void handler(int sig)
{
printf("recv a sig=%d\n", sig);
exit(EXIT_SUCCESS);
}
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("connect");
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork");
if (pid == 0)
{
char recvbuf[1024];
while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(sock, recvbuf, sizeof(recvbuf));
if (ret == -1)
ERR_EXIT("read");
else if (ret == 0)
{
printf("peer close\n");
break;
}
fputs(recvbuf, stdout);
}
close(sock);
kill(getppid(), SIGUSR1);
}
else
{
signal(SIGUSR1, handler);
char sendbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(sock, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
close(sock);
}
return 0;
}
p2psrv.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void handler(int sig)
{
printf("recv a sig=%d\n", sig);
exit(EXIT_SUCCESS);
}
int main(void)
{
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
/* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt");
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen");
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn;
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
ERR_EXIT("accept");
printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork");
if (pid == 0)
{
signal(SIGUSR1, handler);
char sendbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(conn, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
printf("child close\n");
exit(EXIT_SUCCESS);
}
else
{
char recvbuf[1024];
while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
if (ret == -1)
ERR_EXIT("read");
else if (ret == 0)
{
printf("peer close\n");
break;
}
fputs(recvbuf, stdout);
}
printf("parent close\n");
kill(pid, SIGUSR1);
exit(EXIT_SUCCESS);
}
return 0;
}
makefile:
.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=echosrv echocli echosrv2 p2psrv p2pcli
all:$(BIN)
%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o $(BIN)